Winnow Web Design Nottingham

AJAX - a run-through for the rest of us

Dualism in Design

The classic clash: one very small version of yourself sits on your right shoulder, whispering design ideas; another very small version on the other shoulder, whispering technical constraints.

A few thousand years ago in Egypt, someone said, "I want my tomb to be a stupidly big square-based pyramid. Made of stone. In the desert. Yes, I know there's no stone in the desert."

Today, I want a clean site design, constructed in XHTML Strict, pure CSS, perhaps a bit of gracefully failing Javascript manipulating the HTML DOM for fanciness.

In the future, I want to be able to have my site do whatever I think of. Dragging page elements around the screen? Using what information feeds I want, and have them appear in a standard format? Dragging difficult words to a dictionary area? Email notifications popping up on my home page when I receive a new email? The ancient Egyptians threw slaves at their problem. I can write websites which satisfy my technical aesthetics. So why not the future stuff now, too?

Put your hands down

You have probably already spotted a solution. AJAX is here, and it is pretty. It lets us drag stuff (to predefined locations, usually) and pull in whatever feeds the page writer decides on. We can even look up postcodes in the UK; why not use AJAX to satisfy your needs?

Well, there is no why not. Except that most examples of this stuff are being done by technical people. An emphasis of construction over presentation. Worse still, an emphasis of technical proof of concepts over designed solutions to problems which AJAX can solve.

So, here is my effort. Perhaps to get the ball rolling a bit. I leave the incredible presentation to others, as I am not so gifted in that area. But I can see some possibilities, and I will briefly describe this one. I mentioned earlier the ability to drag difficult words into a dictionary area, to reveal a definition. It's a reasonably elegant solution to the problem of deciding which words to include in a glossary: let the user choose. Let's do it in AJAX. Easily.

The Design

Let's set down what we want from this. Some brainstormed ideas:

  • Dictionary's function is obvious to the user.
  • Degrades well with a non-Javascript browser.
  • Does not require changes to the source text.
  • Easily extended with new words and definitions.
  • Manageable: a way of removing on-page definitions which are no longer needed.

Random

One arbitrary design decision: I will use PHP. Another: I will make a basic page which will include the source text. A third: I will make it reasonably pretty.

The PHP

I want this to parse the source text, and add markup to it. This markup will be used in conjunction with AJAX later. So in our main page, let's start it with this (at the very top, even before the DOCTYPE declaration):

<?php $raw_text_file_path_from_index = \ "technical-text/dbd-include.txt"; $wordbin_dir_path_from_index = "wordbin/"; include('dbd/preparser.php'); ?>

Two variables here, for easy changing later. $raw_text_file_path_from_index is exactly that: the path to the content we are going to provide definitions for. $wordbin_dir_path_from_index is the path to the place where the words and definitions are kept. dbd/preparser.php is where we dump the rest of the code. dbd is short for Definition Bin Demonstration.

Let's look at that:

<?php $myDirectory = opendir("$wordbin_dir_path_from_index"); while($entryName = readdir($myDirectory)) if($entryName != "." && $entryName != "..") \ $wordlist[] = $entryName; closedir($myDirectory); foreach($wordlist as $i) $i = chop($i); $textarray = file($raw_text_file_path_from_index); $rawtext = ""; foreach($textarray as $i) $rawtext .= $i; foreach($wordlist as $i) $rawtext = \ preg_replace("/\\b($i)\\b/i", "<span \ class=\"dragme\" id=\"$i\">\\1</span>\ <script type=\"text/javascript\">new \ Draggable('$i',{revert:true})</script>", \ $rawtext, 1); ?>

This requires brief explanation. To keep things simple (and good), each defined word will be in a directory called wordbin/; it will be in an eponymous file, with the definition as the only text. E.g. a file called CSS with its contents as This is the definition for CSS. I hope more thought will go into the definition than that. Anyway. The PHP starts by reading each file name within the wordbin/ directory into the $wordlist array. It then reads the source text (to have definitions added to) into a variable called $rawtext. Finally it does a clever bit of regular expression stuff, where it surrounds each definable word (e.g. CSS) with this markup:

<span class="dragme" id="CSS">CSS</span> <script type="text/javascript"> new Draggable('CSS',{revert:true}) </script>

That's that.

The AJAX

That last bit looked decidedly Javascripty. The more discerning may even know where it comes from. It is time to mention script.aculo.us. Get their incredible framework. I will pop it in a directory called dbd/js. I will also put these lines into the head of my document:

<script src="dbd/js/prototype.js" type="text/javascript"></script> <script src="dbd/js/scriptaculous.js" type="text/javascript"></script>

Built into this is the Draggable element. The PHP registers an element to be draggable. And that's the start. CSS it up a bit so that elements with class="dragme" look a bit different, so people know to drag them, and you should be dragging any words which have definition files (and appear in your source text) round your page.

Now to create the thing to drag them into. I made a div with id="bin" and styled it a bit to give it a fixed width, and a min-height (* html { height: whatever; } for IE). At the end of the HTML document (it must appear after this div) I include some of my own Javascript:

<script src="dbd/js/custom-dbd.js" type="text/javascript"></script>

Inside this file I register #bin to have stuff dropped onto it:

Droppables.add ( 'bin', { accept: "dragme", onDrop: function(element) { newel = 'bin-'+element.id; if(document.getElementById(newel) != null) { p = document.getElementById('bin'); Droppables.remove(document.getElementById(newel)); p.removeChild(document.getElementById(newel)); } new Ajax.Request ( 'dbd/textadder.php', { asynchronous:true, parameters:'word='+element.id, onSuccess:handlerFunc } ); } } );

This adds #bin as a Droppable: you can drop things on it. It is set to only accept elements of class "dragme" which is what we want. When an element is dropped on it, a few things happen. Some error checking: if the definition for that word is already present, we remove the existing definition and replace it. This serves solely to bring the definition to the top of the list.

Secondly, an AJAX (finally!) request is fired off, to the other bit of PHP, at dbd/textadder.php. It's asynchronous - the website does not block until it receives a response - and is POSTed the parameter "word=CSS", to continue our example of "CSS" as the word being defined. If it works, the function handlerFunc (which we have not yet defined) is called. Let's get the remaining PHP out of the way before we get onto that. This is dbd/textadder.php:

<?php $word = htmlspecialchars($_POST['word']); $filename = "../wordbin/".$word; $handle = fopen($filename, "r"); $definition = fread($handle, filesize($filename)); fclose($handle); echo "<div class=\"inbin\" id=\"bin-$word\"><h3>\ $word</h3><p>$definition</p></div> <script type=\"text/javascript\"> new Draggable(\"bin-$word\",{revert:true}); </script>"; ?>

This grabs the wordname (we will stick with "CSS"), looks it up in wordlist/CSS, and inserts it into some code, which is returned looking like this:

<div class="inbin" id="bin-CSS"> <h3>CSS</h3> <p>The definition of CSS as read from the file</p> </div> <script type="text/javascript"> new Draggable("bin-CSS", {revert:true}); </script>

This gives us some nice text which can be referred to in CSS via its class and id. It is also registered as Draggable. Why do we want to drag a definition? Well, my final design requirement was to be able to remove definitions from the dictionary. More on this later.

Back to the main source code, and we need to have another function in dbd/js/custom-dbd.js:

var handlerFunc = function(t) { new Insertion.Top('bin', t.responseText); }

This adds the returned text from textadder.php to the top of the element bin. Great.

Almost there. Now, we will make another element with id="remove". This will be what definitions can be dragged onto to be removed. Style as appropriate, and I added this into my dbd/js/custom-dbd.js:

Droppables.add('remove', { accept: "inbin", onDrop: function(element) { Droppables.remove(element); new Effect.Puff(document.getElementById('remove')); element.parentNode.removeChild(element); window.setTimeout( "reappear(document.getElementById\ ('remove'))", 1000 ); window.setTimeout("resize(document.getElementById\ ('remove'))",1500); } });

This only accepts elements dragged from the definition bin, and does some cute animation with the remove element when it's dropped onto. It also (obviously) removes the definition div from the definition bin.

A couple of final functions, regarding the cute animation. Put them in dbd/js/custom-dbd.js. Don't worry too much about them, although you will have to tweak them if you use the effects. They counter what happens when an element is dropped onto the remove element during an animation:

function reappear(element) { new Effect.Appear(element); } function resize(e) { if(e.style.height != '50px') { e.style.height = '50px'; e.style.width = '50px'; e.style.left = '255px'; e.style.top = '10px'; } }

That's it. Take a look. The icons were ten minutes (you can tell) in Inkscape. Want to add a word definition? Just add one file and refresh the page in your browser. Simple. Want it to degrade a bit better? Make Javascript create the Definition Bin, so non-JS users never get confused. This is just a framework for bigger things. Web 2.0 hype aside, I think we can go further with the web in making web pages feel more like applications, and do what the user wants. We can make things that satisfy the designer and the engineer in us. This was just a simple example from a young guy with relatively little experience; no doubt you can create something far more useful and lasting. I'd love to see it happen.

Article last modified: Thursday 09 March 2006

All website content copyright Robert Grant 2005