[]RSS

About Archives Artwork Comic Contact Philosophy Projects Tags

Unobtrusive web applications

Some notes on web development with JavaScript, XHTML, CSS, standards compliance, and other fragments. Also known as AJaX, when combined with XMLHttpRequest communications.

Unobtrusive JavaScripting

JavaScripts can be added to pages and page elements in a number of ways. The simplest approaches hook the scripts right into the page elements in the HTML, in the DOM element handlers (<div onclick="a_func()">). The in-HTML approach couples page design and script development, which complicates development and maintenance. A cleaner approach separates the script entirely from the page, and hooks the event handlers using the event subscription API. It’s portable between all recent browsers, and decouples things nicely.

Even better, the approach is unobtrusive; it can be done in a way that reads very naturally in the HTML. You can tell an HTML element that it is scripted using a CSS style tag:

<h1 class="expandable">This is an expandable heading!<h1>

To enable this scripted feature, you need to add a JS dependency to the page (in the header). The JS will search the page for all expandable page elements and magically attach itself to each of them. To a page designer, it feels like CSS, giving it a sense of familiarity and comfort.

Examples

The following examples are from a prototype that I’ve been working on, to test a various approaches to JS and AJaX applications. There here mostly so I don’t forget how each approach was useful.

Example 1: Register handlers

This example shows a portable startup event registration, and an event registration hook function. The hook function finds tagged HTML elements and registers events for them, which is the essence of the unobtrusive method. This allows an HTML author to toggle scripting of a page element with simple CSS tagging.

This was my second attempt at initial event registration, which fixed various browser incompatibities. You’ll notice that IE doesn’t support the W3C addEventListener API, which supports multiple listeners for a given event. This limits a few things that can be done in IE, like completely decoupling the startup sequence for multiple separate javascripts.

// Registers handlers for HTML elements with the class 
// of tools in the content DIV
function regPostHandlers() {

    // find DIVs in the content section and look for 'tools' DIVs
    var divs = 
      document.getElementById("content").getElementsByTagName("div");
    for(var ii=0; ii < divs.length; ii++) {

        var div = divs[ii];
        var cid = div.className;
        var id = div.id;
        var name = div.nodeName;

        if (cid == 'tools' && id) {

            // Register menu handlers, using IE-compatible method

            div.onclick = handleToolbar;
            div.onmouseover = handleToolbar;
            div.onmouseout = handleToolbar;

        } 
    }
}

// Startup event, fired after page loads
function startup(evt) {
    regPostHandlers();
}

// register startup event
if(window.addEventListener) // Moz/Opera/Konq/Safari
    window.addEventListener("load",startup,false);
else                        // IE 
   window.onload = startup;

Example 2: Iterative class swapping

This is one of a few approaches for altering page content. It works by swapping the style selector for an HTML element between a set of selectors which are defined in CSS. This approach is useful when there are multiple styles that need to change, where the styles will likely be changed as the site layout changes.

The example toggles the element class between 4 CSS class selectors, to toggle between display/hidden and mouse-over states for both. It toggles the state for the DIV containing an H2 (menu title) and UL (menu list) that is passed as the target event.

// Fragment of an event handler function registered to capture a 
// set of events for a menu
//  * the event has been portably extracted into local vars
//  * events will be fired for each node in the registered HTML node

if (name == 'DIV') {  // handlers for menu DIV events

  if (type == 'click') {  

    if (cid == 'tools' || cid == 'tools_over')
      targ.className = 'tools_hide';
    else if (cid == 'tools_over' || cid == 'tools_over_hide') 
      targ.className = 'tools';

  } else if (type == 'mouseover') {

    if (cid == 'tools')
      targ.className = 'tools_over';
    else if (cid == 'tools_hide')
      targ.className = 'tools_over_hide';

  } else if (type == 'mouseout') {

    if (cid == 'tools_over_hide')
      targ.className = 'tools_hide';
    else if (cid == 'tools_over')
      targ.className = 'tools';
  } 

} 

Example 3: Style toggles

This example is a bit more intrusive, as it touches node styles. If taken too far, this approach would embed specific styling in the javascript. It can be kept simple, though, and can be used to discretely toggle visibility and similar.

The only caveat for unobtrusive toggling the display style is that there are two ways to make a node visible, block and inline, depending on how the block is intended to be styled. This is a bit coupled, but the approach is much simpler than predefined-style swapping, and is useful where you want to hide/show divs where the display type is unlikely to change (like for DIVs, but not for ULs).

Note that the example could be improved to find a single element, rather than toggle all of the lower-level elements.

/*
Toggle Menu (from event target)

    targ is an event target object (an html node)
    state is the desired CSS display state (none, inline, block)
*/
function toggleMenu(targ,state) {

    var nodes = targ.childNodes;
    for (var ii = 0; ii < nodes.length; ii++) { // touch all nodes
        var n = nodes[ii];
        if (n.nodeName.toLowerCase() == 'a') {  // finds A(nchor) elements          

            if (n.style)  n.style.display = state;
        }                                                                                                                         
    }
}

Example 4: Logging

This is a good example of using CSS, XHTML and unobtrusive JavaScript. It provides a debug logging window that can be written to using a simple JS function.

The HTML

Some non-obtrusive HTML to define the logging window. The #log ID defines this as the page’s log window, which assings it the #log style, and #log JS functionality defined below.

<h2>Debug log</h2>
<div id="log"><ul>
  <li>This is the first log entry!</li>
</ul></diV>

The CSS

The #log DIV has a fixed height with overflow turned on, which adds the nice little scrollbar when the log window grows past 20 lines. The list bullets are turned off, and a fixed font is used to make the window look debugish.

#log { border: 1px solid #CCC; font-family: ‘Lucida Console’, monospace; font-size: smaller; width: auto; height: 20.5em; overflow: auto; background-color: #EFEFEF; color: #0A0A0A; }

#log ul li { list-style-type: none; list-style-image: none; }

The JS

The function finds the first DIV with the id LOG and grab’s its first UL. Log entries are made to the unordered list by adding the text inside new LI elements.

function log(txt) {
    out = document.getElementById("log").getElementsByTagName("ul")[0];
    if (!out) return;
    li = document.createElement("li");
    li.appendChild(document.createTextNode(txt));
    out.insertBefore(li,out.firstChildZ);
}

Extension ideas

  • Add async logging to server (from the same API)
  • Allow toggle, clear, ’save’ of log window
  • Add a pre-processor step to optionally strip all log calls

Unobtrusive AJaX

(TODO: document after testing prototype)

Notes, tips, tricks

OnLoad

body onload="" over-rides an in-script onload functions in IE 5.5/6.0+

Event Properties

Event properties are not portable between browsers, the following snip shows a portable way to get a useful subset of properties as described at

// IE doesn’t pass events to handlers, instead it uses window.event if (!evt) var evt = window.event;

// IE calls target srcElement if (evt.target) var targ = evt.target; else if (evt.srcElement) var targ = evt.srcElement;

// Node name may not exist if (targ.nodeName) var name = targ.nodeName; else var name = ‘no_name’;

// Generally, the DOM stores values as tuples (type, data), so // data is always the lastChild if it exists if (targ.lastChild) var val = targ.lastChild.nodeValue; else var val = ‘no_val’;

// Other properties are standard in all browsers var type = evt.type var id = targ.id; var cid = targ.className;

References

DHTML

Javascript

This is a set of links to unobtrusive, portable Javascripting information.

Ajax / XMLHttpRequest

Ajax (coined by ) is a term for Asynchronous JavaScript and XML, as used in applications such as Gmail and Google Maps.

Full examples

Tools

Inline edit themes for WP

I found a few themese for WP that are starting to use AJaX bits, similar to one I’m working on.

  • http://www.asymptomatic.net/archives/2005/03/17/1400/artychoke-theme-preview/#comments
  • http://twilightuniverse.com/2005/03/wordpress-touched/

Saved Searches

A few searches to find the above links.