Archive for February, 2007

Unobtrusive JavaScript made possible

Unobtrusive Javascript explained

The usual way of writing JavaScript in templates results in obtrusive code. For instance, the classing link_to_function() helper called in symfony like this:

<?php use_helper('Javascript') ?>
<?php echo link_to_function('see the image', 'showPopup('image.jpg')') ?>


The code appearing in the template is:

<a href="#" onclick="showPopup('image.jpg'); return false;">see the image</a>


While this code effectively provides the correct interaction, it contains an inline even handler (onclick) which will create problems with screen readers. Additionally, the CSS revolution taught us to separate content from presentation, and we soon understood the benefits this could bring (reusability of the presentation layer, better maintainability, etc). Following the same concept would naturally lead to separating behavior from content, and this means putting away JavaScript code. Accessibility and layer separation recently led to a new way of using JavaScript called unobtrusive scripting. The term comes from Stuart Langridge, who started the movement in 2002, rapidly followed by JavaScript gurus like Peter-Paul Koch.

The problem with unobtrusive JavaScript is that it is much longer to design and write. You must first deliver a Javascript-free XHTML content, which must be able to run without further addition, then execute JavaScript code on it to modify some elements, add event handlers, etc. This should sound familiar to those used to styling through CSS, but look at how long it takes:

<head>
  <script type="text/javascript" src="my_behaviors.js"></script>
</head>
<body>
  <a href="image.jpg" id="foobar">see the image</a>


// in my_behaviors.js
function initializePage()
{
  var x=document.getElementById('foobar');
  x.onlick = function () { showPopup('image.jpg'); return false };
}
window.onload = initializePage;


The link works even if JavaScript is disabled. But in order to modify if afterwards, we must add an id attribute to it, then we must register its onclick event handler in JavaScript, and then make sure that the JavaScript is launched when the DOM is ready, therefore executing it when the onload event fires. This is the correct way to achieve the same effect as the first listing in an unobtrusive way.

If you ever tried this, you probably thought just like me: the hell with unobtrusiveness, there is no way I'll spend the rest of my life writing ten lines instead of one just for the sake of accessibility and layer separation!

Fortunately, there are two tools that could make you change your mind.

The first is a JavaScript framework. The most well-known (because it's the official js framework of the most well-known web application framework) is Prototype, but you could as well consider jQuery or others. Doing unobtrusive JavaScript (ok, let's call it UJS until the end of this article) with a JavaScript framework is a lot more easier than with plain JavaScript. See how the previous listing could be reduced by using jQuery:

<head>
  <script type="text/javascript" src="jquery.js"></script>
  <script type="text/javascript" src="my_behaviors.js"></script>
</head>
<body>
  <a href="image.jpg" id="foobar">see the image</a>


// in my_behaviors.js
$().ready(function(){
  $('#foobar').click('showPopup('image.jpg')');
 })


The jQuery dollar function can use a CSS3 selector to find a DOM element, and the addition of a handler is greatly facilitated by the click method. Also, the ready method fires when the document is ready, but you can attach more than one funciton to it.

But that's not enough. This is still way longer to write than the obtrusive version, which was using a single call to a PHP helper. But why not use a helper to add UJS code? This could be very easily done. The syntax could look like this:

<?php use_helper('UJS') ?>
<a href="image.jpg" id="foobar">see the image</a>
<?php UJS_add_behaviour('#foobar', 'click', 'showPopup('image.jpg')') ?>


The UJS helper could very well manage storing the UJS code in the session and writing it in a separate file that would be called by the first template, transforming this listing into the previous one. In fact, I've written a symfony plugin called sfUJSPlugin that does exactly that.

It goes event beyond: If you use the regular symfony helpers, every mention of an event handler property gets automatically transformed into UJS code. So the same effect as before can be achieved with just:

<?php use_helper('UJS') ?>
<?php link_to('see the image', 'image.jpg', 'onclick=showPopup('image.jpg')') ?>


This is not longer than the first example, and that's unobtrusive. At last, we have the right tools to make the web both accessible and usable.

Cross-Domain Ajax with symfony

I recently released a new symfony plug-in, called sfWebBrowser. It's a lightweight HTTP client written in PHP, that doesn't require any additional module to work, and it opens a lot of interesting possibilities. As a matter of fact, it doesn't even require symfony to work - you can use it on any application, with any framework. Refer to its wiki page for installation instructions.

Many web 2.0 applications reuse content from other sites, mostly through REST web services and RSS feeds. The sfWebBrowser plugin already allows you to do that in a very simple way. For instance, to use the symfony blog news feed as a data source, you just need these three lines:

$b = new sfWebBrowser();
$b->get('http://www.symfony-project.com/weblog/rss');
$xml = $b->getResponseXML();


The $xml variable contains a simpleXML version of the online feed, which you can parse easily to retrieve the data you need for your own application.

But haven't you ever thought about a web application that you like in these terms: "Damn, if only they had an API to let me reuse this on my site"? With an HTTP client and the powerful parsing capabilities of PHP, the whole web becomes a giant API.

Take google suggest, for example. They do seem to have a nice API, but as it is not official and not the purpose of this post, we'll voluntarily ignore it. Instead, let's see how to use the publicly available version. When you type characters in a google suggest search box, AJAX requests are sent to:

http://www.google.com/complete/search?hl=en&client=suggest&js=true&qu=WhateverYouTyped

The result is a string meant to be executed in JavaScript:

sendRPCDone(frameElement, "symf", new Array("symphony", "symfony php", "symfony project",
"symfonia", "symfony framework", "symfony tutorial", "symfony-project", "symfw.sys", "cmfce",
"symfony plugins"), new Array("2,540,000 results", "1,700,000 results", "728,000 results",
"1,560,000 results", "979,000 results", "781,000 results", "167,000 results", "817 results",
"967 results", "768,000 results"), new Array(""));


This is quite easy to parse in PHP. So in order to use this output in an AJAX input_auto_complete_tag on your site, you would have to use your server as a proxy to google suggest. Let's see how to do that in symfony. First, create a form with autocompletion on a 'search' input. That's pretty straightforward with a JavaScript helper:

// in mymodule/templates/indexSuccess.php
<?php use_helper('Javascript') ?>
<?php echo form_tag('search/search') ?>
  <label for="search_string">Enter text here:</label>
  <?php echo input_auto_complete_tag('search_string', '',
    'mymodule/autocomplete',
    array('autocomplete' => 'off'),
    array('use_style' => 'true')
  ) ?>
  <?php echo submit_tag('submit') ?>
</form>


Then, in the mymodule/autocomplete action, use the sfWebBrowser to make a request to the google suggest URI, decode it, and prepare data for the template:

// in mymodule/actions/actions.class.php
public function executeAutocomplete()
{
  $service_uri = 'http://www.google.com/complete/search?hl=en&client=suggest&js=true&qu=';
  $b = new sfWebBrowser();
  $b->get($service_uri.urlencode($this->getRequestParameter('search_string')));
  preg_match('/new Array\((.*?)\)/si', $b->getResponseText(), $matches);
  $suggestions = array();   
  foreach(explode(',', $matches[1]) as $suggestion)
  {
    $suggestion = trim($suggestion);
    $suggestions[] = substr($suggestion, 1, strlen($suggestion)-2);
  }
  $this->suggestions = $suggestions;
}


The final part is the autocompleteSuccess.php template. It's as simple as it can be:

// in mymodule/templates/autocompleteSuccess.php
<ul>
<?php foreach($suggestions as $suggestion): ?>
  <li><?php echo $suggestion ?></li>
<?php endforeach; ?>
</ul>



And that's all. The search box of your website now uses the response from the google suggest application to build its own suggestions. Did I say cross-domain AJAX? That's what it is, though. The JavaScript autocomplete control executed in a browser on a page of your domain uses results from another domain, your server (and the sfWebBrowser) being a proxy between the two.

Imagine all you can do with such a tool... "Mashup" is the word, I think.

Rename Cookies

Websites use cookies. It's a nice name for something that can be really nasty. It's also a good idea to use some kitchen metaphors in a world full of office metaphors.

But using the word "cookies" in the web context leads to some strange stuff like:

Do you want to clear your session cookies?

or even:

Accepting cookies can endanger your privacy

Today is the opportunity to change the cookies name. We just have to figure out the best way to name them.

If our primary concern is to keep a sound relation to the current word, I propose

Cuckoo

or

Kickin

We would understand better why "accepting cuckoos can endanger your privacy" and "beware of session kickins".

If our primary concern is to keep the kitchen metaphor, then I propose:

Soufflé

or

Cheese nan

It doesn't make more sense than cookies as far as web browsers are concerned, but at least it emphasizes the fact that cookies are hot, hard to bake and delicate (for the first word) or exotic, indispensable and round (for the second one).

But being a revolutionary is all about pushing the limits - at least that's what Guy Kawasaki says. So while we are at it, why not allow ourselves to rename cookies without any constraint related to the current name?

That's a great idea. And it leads to great names, too. Judge for yourself:

morning-after pill (because when you look for it, it is often too late)

smut (because of all the dirt that's into)

disclosure (so that you don't forget about it)

eskimo (because it is very resistant)

janitor (because it remembers you)

Now, what's your suggestions about the best new name for web cookies, in an ideal world?