Unobtrusive web applications
Note: This page is from 2004, scraped from my old wiki. It’s a set of notes on simple web programming, before libraries like prototype and jquery were widespread.
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-scriptonloadfunctions 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 Quirksmode.org
// 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.
- Event compatibility
- Sortable tables
- Expanding lists
- IE W3C tests
- Showing and hiding DIVs
- Nifty css load example
- http://icant.co.uk/forreview/popstuff/popstuff.html
- http://www.snook.ca/archives/000291.html
- Instant-switching tabbed view
- Event ordering problems
- DnD lists tool-man.org/examples/edit-in-place.html
- Neat mouseovers
- Div hiding
Ajax / XMLHttpRequest
Ajax (coined by AdaptivePath) is a term for Asynchronous JavaScript and XML, as used in applications such as Gmail and Google Maps.
- About Ajax article (from John)
- Ajax article
- Portable XML requests
- Applying concerns to JavaScript with Ajax
- AJAX links
- Javascript links
- More AJAX links
- Simple XML HTTP Request tutorial
- XML Http Request core docs
- XMLHttpRequest, Ajax, and the customer experience
- XMLHttpRequest guidelines
- Large list of working examples
- http://www.ajaxmatters.com/
Full examples
- Google suggest-style combo-box
- Dom-scripted lists (many other good bits here)
- Dynamic option lists
- Mozilla.org DOM samples (not all portable)
Tools
- Javascript ‘compiler’ Packs scripts for performace/obscurity.
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.
