Elastic usability
16th December 2008, one commentWhile frantically finishing my Christmas shopping; I had an odd turn. Amazon hadn’t removed their search box, had they?
Amazon.co.uk has always been gifted with an elastic layout, that is, a page layout that resizes to fill the full width of the browser viewport. The home page HTML has recently been improved, dragging the elastic layout from the table-based mess it once was, kicking and screaming into the CSS era. Bravo.
In support of elasticity
One of the reasons for implementing an elastic layout is that it will fill the viewport on all resolutions, thereby giving all users an optimal browsing experience. In the world of e-commerce, and particularly with Amazon’s rich inventory range, showing more items on a page will likely convert to a higher number of sales.
When casually browsing, like many others, I have my browser open at almost the full width of my 1680px monitor. Most site layouts are centred horizontally and employ a fixed width (or a maximum width, at least) to keep line lengths short and give pixel precision for replicating the designer’s hard work. However Amazon spills across my screen and fills my eyes with glorious Christmas cheer.
Only, upon reaching the home page, I had a surprise when I couldn’t see the Search bar usually towards the top and left of the header area. It was there, but I looked straight past it. On a wide screen the search box expands to fill the void, visually becoming a series of horizontal keylines; almost part of the visual design.
Figure 1. Amazon.co.uk at 1024px wide, narrow search box
At the wider size my eye doesn’t scan to the far right to see the orange “Go” button, and I miss that it is a search box at all:
Figure 2. Amazon.co.uk at 1680px wide, wider search box loses its form
Affordance
The wider search box reduces the level of interactive affordance. Affordance is the quality of an interface suggesting its level of interactivity. In this example the affordance of the search box is reduced in two ways:
- The keylines and white background appear as more of a design feature than an interface element, thereby masking its functionality entirely
- A longer text box suggests to a user that a longer search phrase is expected of them
Repeat visitors to Amazon will know the location of the search from previous visits. However a new visitor could mean that users miss the search entirely. Search boxes on other sites tend to be a lot narrower — Google for example — therefore a user may scan for something of a similar size.
This second point above is important, since having to type a longer search phrase is likely have the result that the phrase is either overly specific or overly vague. Both of these have the effect that the search results are likely to be less relevant, therefore providing a poorer searching experience.
Max-width and the elastic limit
This simple interface problem can be solved by applying a maximum width to the search box. A maximum width would ensure that the input element remains elastic up to a certain width, above which a fixed size is be used.
The max-width property in CSS allows developers to achieve this with ease (although a hack for Internet Explorer 6 is required, naturally):
input.search {
max-width: 300px;
}
Although an arbitrary width could be used, this should ideally be a direct result of both user testing and analysis of search logs to determine average search phrase lengths with the highest conversion rates.
Have we reached the elastic limit?
Although the implementation of elastic layouts is not new, there has been recent renewed discussion of the future of elastic widths. The latest versions of major browsers: Internet Explorer 7 and 8, Firefox 3, Safari 3 and Opera 9, include resizing or zooming functionality to scale pages up when fixed pixel font sizes and widths are used. This has the effect of making elastic and liquid layouts redundant, since the browser is doing the hard work so the CSS developer doesn’t need to.
I borrow from the WCAG 1.0 guidelines the often-cited phrase “until user agents support…”. In the context of WCAG 1.0, this terminology is used to suggest that the recommendations are making up for shortfalls in user agents (browsers and the like); and once the problems are solved by the user agents natively, the guideline essentially becomes irrelevant to a developer.
Using the same principle, it is my opinion that this usability and accessibility problem is now being solved by the browser manufacturers (albeit with varying degrees of success). As designers and developers we shouldn’t feel guilty when implementing fixed width layouts. Not one ounce.
Archiving XML with Symphony
27th November 2008, 15 commentsSymphony is an incredibly powerful CMS, but it’s ability to cache dynamic XML feeds is lacking. In this article I explain a simple method of parsing a third-party XML feed and archiving its contents in native Symphony sections.
Over the last six months or so, Symphony content management system has become my framework of choice. The reasons are numerous and deserve their own blog post entirely; but the foremost reason is that Symphony allows a developer to define their own data structures, query it, and have the output served as well structured XML to be formatted into XHTML using XSLT views.
With such a focus on XML, it made sense to provide a “Dynamic XML” data source type that pulls an XML feed directly into the CMS. While this native functionality is useful for grabbing a feed of most recent Flickr photos or Twitter posts, I have found it insufficient in several respects:
- if the XML feed is unavailable or invalid at the time of caching, the data source will fail
- content in the feed is not archived — when content is removed from the remote feed, we can no longer access it
In order to solve these problems (as alluded to on the Overture forums) I have used a method of saving items from the remote XML feeds into native Symphony Sections, so that they are archived and can be queried as native entries.
In the following example I’ll explain how to archive your Twitter timeline into Symphony.
Section
The data is to be stored in a Section, so begin by creating a Section with the same data structure as the feed. In my example I have created a “Tweets” section with the following fields:
- ID (Textfield)
- Message (Textarea)
- Date (Date)
Figure 1. Tweets section
Event
Create the event “Save Tweets” from the Tweets Section just created. Make sure the “Allow Multiple” option is selected, so we can save more than one tweet with a single request.
Figure 2. Save Tweets events
Data Source
When saving tweets to the Tweets section, we need to be able to check if the tweet already exists in our cache. Thankfully the Twitter API assigns a unique ID to each tweet that we can use. If an RSS feed is being used the permalink or guid elements are suitable alternatives.
Once the Twitter feed has been loaded, we need to compare the XML against existing entries in Symphony. Therefore create the data source “Cached Tweets” to select the most recent tweets. We don’t need the full entry, just the ID element.
Figure 3. Cached Tweets data source
Page
Create the page “save-tweets” and attach the Save Tweets event and Cached Tweets data source to it. We need a smidgem of XSLT to output the data source as XML. This should do it:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="data">
<xsl:copy-of select="cached-tweets" />
</xsl:template>
</xsl:stylesheet>
Figure 4. Save Tweets page
The end result is a page that outputs the latest tweets as XML, and that also acts as a form handler to accept POST requests to add new tweets. All that remains is the brains of the operation — some custom PHP goodness.
The script is self-explanatory, but here’s the outline:
- Fetch the XML feed of cached tweets from Symphony (created above)
- Fetch the latest tweets from the Twitter API
- Iterate through status update in the Twitter feed
- Check whether an entry containing the same tweet ID exists in the Symphony XML
- If no entry exists, build multi-dimensional post variables to send to our page
cron.save-tweets.php
<?php
$page = "http://yourdomain.com/save-tweets/"; // path to Symphony page to output latest cached tweets and has the Save Tweet event attached
$twitter_username = "username"; // screen name (e.g. twitter.com/username)
$new_tweets = array();
// Get the most recent cached tweets from Symphony
$symphony_feed = DOMDocument::load($page);
$symphony_tweets = $symphony_feed->getElementsByTagName("entry");
// Get the most recent tweets from Twitter
$twitter_tweets = DOMDocument::load("http://twitter.com/statuses/user_timeline.xml?id=$twitter_username");
// Loop through the Twitter tweets
foreach ($twitter_tweets->getElementsByTagName("status") as $tweet) {
try {
// Get tweet information from the XML, format date
$tweet_id = $tweet->getElementsByTagName("id")->item(0)->nodeValue;
$tweet_text = $tweet->getElementsByTagName("text")->item(0)->nodeValue;
$tweet_date = date("d F Y H:i", strtotime($tweet->getElementsByTagName("created_at")->item(0)->nodeValue)); // Mon Aug 18 09:38:05 +0000 2008
// Query the cached tweets XML using the tweet-id field to see whether the entry already exists in Symphony
if ($symphony_tweets->length == 0) {
$cached_tweet = 0;
} else {
$xpath = new DomXPath($symphony_feed);
$cached_tweet = $xpath->evaluate("count(//entry[id='" . $tweet_id . "'])");
}
// If the count() was zero, it is a new tweet so add to the new tweets array
if ($cached_tweet == 0) {
array_push($new_tweets, array($tweet_id, $tweet_text, $tweet_date));
}
} catch (Exception $ex) {
var_dump($ex);
}
}
// Set up initial POST variables
$post = "MAX_FILE_SIZE=104857600&action[save-tweets]=Submit";
// Build post variables for each new tweet
for($i=0; $i < count($new_tweets); $i++) {
$post .= "&fields[$i][id]=" . $new_tweets[$i][0];
$post .= "&fields[$i][message]=" . urlencode($new_tweets[$i][1]);
$post .= "&fields[$i][date]=" . $new_tweets[$i][2];
}
// if there are new tweets, send a single POST request to the page
if (count($new_tweets) > 0) {
$save_tweets = curl_init();
curl_setopt($save_tweets, CURLOPT_URL, $page);
curl_setopt($save_tweets, CURLOPT_POST, 1);
curl_setopt($save_tweets, CURLOPT_POSTFIELDS, $post);
curl_setopt($save_tweets, CURLOPT_RETURNTRANSFER, TRUE);
curl_exec ($save_tweets);
curl_close ($save_tweets);
}
?>
What is left to add is that this PHP script should be scheduled to run as a cron task on your server (the frequency of which depends on how much of a Twitter-aholic you are!).
There you have it — a relatively simple method to archive data feeds into Symphony. Imagine the possibilities of archiving references to all of your Flickr photos, or the ability to mine years of Last.fm plays. Thanks to Symphony’s entirely flexible structure, you can recreate almost any data schema and with some rudimentary PHP to perform basic XML DOM manipulation, you end up with a pretty powerful system.
Focus on — Accessibility
25th November 2008, 2 commentsWCAG 1.0 provides authors and developers with useful guidelines to follow to improve content accessibility; but following these rigidly can, on occasion, have the opposite effect.
I had a short article published in the March 2008 issue of .net Magazine entitled “Focus on… Accessibility”.
Figure 1. Focus on... Accessibility article from .net Magazine, March 2008