ZOIS *
Technical Notes
ZOIS Technical Note TN-2009-11-15.
Author and Audience
As the 'front-end' of web interaction becomes more
and more important this TN presents some little experiments in that
direction made here. In particular dynamic content and animation are
explored. The reader is assumed to be familiar with programming
techniques, if not fully conversant in Javascript. Written by Martin
Sullivan[au], ZOIS Limited,
Cockermouth.
Abstract
Ajax, a term encompassing a number of web front-end technologies
has become increasingly important, forming as it does, the core of so
called "Web 2.0" interactive user-content based web-sites. Although
Ajax itself is not formally defined, its current understanding is
rather complexed. It need not be and this paper shows how a simple
dynamic web-site can be constructed with it by cutting a few corners
and ignoring some of its inherent complexity.
Introduction
Much of the Web-2.0 hype surrounds Ajax. Ajax[aj]
is a somewhat loose term that encompasses accessing both the Internet
in general and manipulating a displayed web-page using
Javascript. Specifically it has come to mean altering the style,
content and presentation of the page together with using XML and
XMLHttpRequest to implement an RPC like mechanism. The
result is dynamic pages that do not require reloading in their
entirety when interacting with some back-end. The term itself was
coined by Jesse James Garrett in 2005[ap], although
the technologies had existed prior to that.
Once one has read a little around the subject and, perhaps, tried a little code it is soon realised that Ajax can be quite convoluted and in particular, very Browser specific. Javascript, and indeed most of the rest of Ajax, can be a bit of a movable feast, and although things are slowly improving its support in older browsers can be idiosyncratic. These browsers do not go away because you, as hot hip programmer, want to use some fancy feature. The result is that a customer can be looking at an ugly mess in something that is only a few years old. Several versions of the code catering for several different browsers, but all doing basically the same thing are the result. Code can quickly become quite complicated and ugly and bug riddled. The communities response to this is to jacket things up in standard libraries, with these quirks hidden from the innocent programmer, who is free, quite rightly, to concentrate on the higher things. In the authors opinion, inconsequential though it is, the best of these libraries is probably jQuery[jq]. But even this fine piece of code can be quite daunting. It can seem complicated, overblown and learning curve is steep. Well, it is to old, decrepit FORTRAN programmers. Like any jacketing technology it can take you by the hand and lead you to create stuff that looks just like the examples on the original authors web-site. Which is good, but may not be what you intended.
It was therefore resolved to explore both Ajax (as loosely defined)
and related technologies (think things that make may make Ajax
redundant). The results of these experiments are presented here.
Materials and Platform
Programming all this occurred in Javascript and Firefox was the
target platform (specifically 3.0). Testing occurred on an iMac (for
Safari), and a collection of Internet Explorers, from version 6 to the
current version 8. This however cannot be considered comprehensive,
for important new browsers such as Chrome and Opera remain
untested. As an experiment Lynx was used to assure people using this
fine text-only browser that they would not be inconvenienced by
techniques suggested by these experiments. As ever the primary
development environment Emacs running on Linux. A PostgreSQL based PHP
Apache environment supplied the dynamic content.
Method
For conveniences sake we'll separate this into animation and dynamic data acquisition.
The Ajax orthodoxy has it that XML is used to provide a data
representation structure that is delivered using
XMLHttpRequest. This data is then used to update the
Document Object Model (DOM) using Javascript on this directly. It is
also used in parsing the bits that interest from the XML message and
massaging them into a form that can be inserted into the correct bit
of the DOM. The DOM itself can be considered a tree-like structure,
with instances that can be set and retrieved by addressing the name of
the elements, often marked up with DIV or
SPAN naming conventions. This can seem awfully
complicated when all that is required is a small piece of text that is
presented retrospectively, either after the initial page has been
loaded (to speed loading times) or in response to some event such as a
mouse-over or a button-press. While all this data manipulation is good
and required to give full expression to the Ajax ideal, a simpler
method is simply to deliver targeted preformatted HTML and directly
inject it into the page. Although its name might suggest otherwise
XMLHttpRequest is happy with this, and will cope with
what is effectively mal-formed and illegal XML. This delivers a
sizeable amount of the dynamic-data functionality but in a rather
simpler fashion. That having been decided, it is found that others on
the web have formed a similar conclusion and already named such an
approach 'AHAH'[ah]. Such is the nature of the
Internet.
There are a number of approaches which although not novel in
themselves go together in this simplification. Earlier Microsoft
web-browsers were poor in their support for the key
XMLHttpRequest. This is largely solved by loading an
ActiveX object. And therefore it was found that the easiest way of
accomplishing this was to define the XMLHttpRequest
should it not be found.
// Provide the XMLHttpRequest class for IE 5.x-6.x:
// Other browsers (including IE 7.x-8.x) ignore this
// when XMLHttpRequest is predefined
if (typeof(XMLHttpRequest) === "undefined") {
XMLHttpRequest = function() {
try {
return new ActiveXObject ("Msxml2.XMLHTTP.6.0");
} catch(e) {}
try {
return new ActiveXObject ("Msxml2.XMLHTTP.3.0");
} catch(e) {}
try {
return new ActiveXObject ("Msxml2.XMLHTTP");
} catch(e) {}
try {
return new ActiveXObject ("Microsoft.XMLHTTP");
} catch(e) {}
throw new Error ("XMLHttpRequest not supported.");
}; // defn. HMLHttpRequest
} // if
|
With XMLHttpRequest defined one can use it. It is an
asynchronous call, in that one makes a request and then some
pre-defined functions are called when a response is completely
received.
function ahah(url, target_id, animation) {
if (document.getElementById(target_id).innerHTML != "") {
animation(target_id); // already got that
return;
} // if
if (window.XMLHttpRequest) {
req = new XMLHttpRequest();
req.onreadystatechange = function() {
ahahDone(target_id);
animation(target_id);
};
req.open("GET", url, true);
req.send(null);
} //if
} // ahah
|
In this fragment, the function ahah uses
XMLHttpRequest to run a remote GET, provided in this
instance by PHP. The PHP function returns pre-HTML formatted code
which will be inserted into the element noted by target_id. Should the
target HTML not be empty then that no HTML is retrieved, but what is
there already is used. Thus the target_id HTML element needs to be
empty if the new content is to be fetched.
When the ahah function is called, a second function,
animation is invoked. This handles the display of the new
or recently acquired content.
Finally there is the ahahDone function:
function ahahDone(target_id) {
if (req.readyState == 4) { // only if req is "loaded"
if (req.status == 200 || req.status == 304) { // only if "OK"
results = req.responseText;
document.getElementById(target_id).innerHTML =
results;
} else {
document.getElementById(target_id).innerHTML =
"Ahah error:\n" +
req.statusText;
} // else
} // if
} // ahahDone
|
This function is run only when there is a response from the remote
server. Should the response be valid then the returned, preformatted,
HTML is injected into a nominally empty HTML element, identified by
ID. In this particular example the HTML is not displayed until the
animation function is run on the ID. Thus the data is
acquired and then displayed in a magical Web 2.0 manner.
Once the data has been acquired by the procedures seen above it
needs to be displayed. The HTML element that the newly obtained
preformatted data is injected into normally is not displayed. The
animation function is run to display this, in a suitably
whizzy way. In our example the element appears as a 'bubble'
uncovering from the bottom. Firstly some Cascading Style Sheet code is
required to initialise the element:
/* optional stuff is dynamically manipulated by Javascript in
* conjunction with a dynamically assigned tag */
.optional {
display: none;
} /* optional */
|
This is identified by a class construct in a DIV HTML element. This DIV identifies the empty HTML element initially and generally by CLASS. The individual element is identified by an ID. The HTML looks something like this:
<div class="optional" id="someIdElement"> |
Having labelled things up one can proceed to the nitty-gritty of animation. In Javascript this is not straight forward, but required a little well known recursive type jiggery-pokery.
var moving = false; // guard flag
var last = null;
function show (s) {
if (last)
hide (last); // debris from last time gone
last = s;
if (moving)
return;
window.status="display text";
t = document.getElementById (s);
t.style.height = ''; /* reset to initial value if run again */
// from here ...
t.style.width = '50%';
t.style.backgroundColor = 'aqua';
t.style.borderStyle = 'solid';
t.style.borderWidth = 'thin';
t.style.left = '20%';
// ... to here defines a bubble like entity
t.style.position = 'absolute';
t.style.visibility = 'hidden';
|
The above fragment mostly sets style elements and could conceivably be replaced by an appropriately labelled style element in a Cascading Style Sheet (CSS). Two important bits of code allow the clearance of debris and ensure that any in-progress animation is allowed to progress to completion. To conclude:
current = 0;
moving = true;
animate_down ();
moving = false;
} // show
|
Function animate_down does most of the work.
function animate_down () {
if (target == current)
return;
t.style.height = current + "px";
current++;
if (current > 1000 && !asked) {
asked = true;
if (!confirm ("Very large animation, continue?")) {
ask = false; // reset
return;
} // if
} // if
x = setTimeout ("animate_down ()", 5);
} // animate_down
|
Animation in Javascript can only be accomplished with a lame tail
recursion that is tracked by a global variables. Thus the current
height of the animation is kept in a variable 'current' and, as
tradition demands, is tested to see if the recursion is completed. If
not the appropriate style element is adjusted and the function
re-called via a timer function, setTimeout. In the above
example some guard code ensures that the animation does not overrun by
crudely checking the current size of the animation against an
arbitrary large value.
function hide (s) {
var t = document.getElementById (s);
t.style.display = 'none';
} // hide
|
The hide function simply removes the element from
view. This was found to be the least surprising. An animation at this
point only slowed things down for the user. The function is designed
to be called in advance of a new call to animate_down,
thereby removing any previously displayed element from the user
experience. Hide is also used to remove distractions when
the user moves away from the element under consideration and when the
user clicks through of the full details. The example HTML for this is:
<a href="jcpdetail.php?reference=WHV%2F6851"
onMouseOver="ahah ('jcpsumm.php?reference=WHV%2F6851',
'WHV%2F6851',
show)"
onMouseOut="hide ('WHV%2F6851')"
onClick="hide ('WHV%2F6851')"
>Outpatient Co-ordinator LEP</a><div class="optional"
id="WHV%2F6851"></div>
|
Thus, putting it all together in the above example (taken from a
real web-site[hm]), Should the user place their
pointer or mouse cursor over the anchored element then jcpsum.php is
run with an appropriate reference. This returns a small piece of
pre-structured HTML and it is injected into the "optional" element
identified by the "WHV%2F6851" ID. The function show is
then called to check and perform a fairly rapid animation that will
cause the element to appear in a bubble like over-box. Should the user
user either click on the anchor or remove the mouse cursor from the
link then the element is perfunctorily removed.
Discussion
The code can be seen in demonstration on the Home web-site[hm]. Since it is interpreted it is open to inspection.
Although it looks simple it is in fact the result of some experimentation on the part of the author. These experiments did allow the formulation of some opinions on the design of Javascript driven web-pages.
The major finding of this work is that Ajax and related technologies add complexity to web-sites that frequently detract from the user experience. The Author is in the habit of visiting web-sites with Javascript switched of, either in its entirety or through the good offices of NoScript[ns]. This means that he frequently visits web-sites that are catatonic or format badly because both style elements and necessary functionality are embedded in Javascript scripts. No less irksome are web-sites that disallow functionality (including trying to sell you things) until you've switched Javascript on. There is presumably a pressing need for the users of such sites to be dazzled by the programming ability of their authors.
So, with the author of this papers somewhat jaundiced opinion to the fore, we can see that he favours a very conservative style of Javascript programming that only enhances an existing web-site. To this end experiments were undertaken that caused large animations to occur, with blocks of text being moved around the page and reformatted on the fly. It was found that support of such features was poor, particularly in older browsers and after an initial 'Wow' phase, irritating.
In our quick-and-dirty Ajax we download a single fragment of preformatted HTML to be injected into the web-page. This works, but it does mean that some parts of the presentation layer is taken away from the browser and Ajax based program. A better solution is to use XML or JSON to add structure to the data and then parse it into the appropriate place in the Document Object Model. This is the 'correct', heavy way of doing things in Ajax. Not only does it allow multiple fetches of data, improving efficiency, but it decouples the presentation layer, which is always a good thing.
Although, in this paper, a simplified Ajax is presented anything
more complicated rapidly becomes challenging. It is particularly true
once multiple browsers are to be catered for. For this reason several
Ajax libraries of Javascript have been produced. The authors favourite
is jQuery[jq], but by no means is it the only
one. There are a number of others and comparison is invited[jf].
References
References found in this section, and in particular the HTML links were correct at time of writing (2010-03-03).
$Date: 2010/03/07 10:17:13 $