Chapter 5. Display Manipulation
FOR AN END USER, THE MOST OBVIOUS THING ABOUT AJAX IS ITS VISUAL APPEARANCE. AJAX APPS TEND to look richer, and the interface tends to update smoothly, more like a desktop app than a conventional web app. This chapter describes the two main technologies for updating the user interface. Display Morphing focuses on changes to a single element, while Page Rearrangement involves changes to the page structure. Along the way, we’ll look at the Document Object Model (DOM). DOM manipulation is key to many of the Ajax Patterns.
Display Morphing
⊙⊙⊙ Display, DOM, Graphics, GUI, Morph, Page, Paint, Reference, Rich
Goal Story
Stuart is answering a question in an online quiz, with a countdown box showing how much time remains. The countdown label changes each second, and the color gradually shifts from green to red as the countdown proceeds.
Problem
How can you dynamically update display elements?
Forces
As the user’s task and context changes, applications need to present different types of information.
As information the user is working on changes, information being displayed becomes stale.
Stale information is a big problem in conventional web apps. Because the information cannot change until it’s out of date, you sometimes see disclaimers such as “May Be Out of Date” or instructions like “Click to Refresh.”
Submitting and redrawing the entire page interrupts the user’s flow.
Solution
Morph page elements by altering styles and values in the Document Object Model (DOM), such as text and color properties. The DOM represents the state of the web page at any time. When JavaScript manipulates the DOM, the browser notices the changes and immediately reflects them in the user interface. To morph a page element’s display, you get a reference to it and change its properties. This allows you to change the display of any element on the page—headings, div elements, images, and even the document object itself. Note: an online demo (http://ajaxify.com/run/display) illustrates the code concepts throughout this Solution and the code snippets loosely follow from the demo.
To further illustrate the DOM, consider the following piece of HTML:
<div id="display"> <img id="photo" src="photo.jpg"/> <div id="dimensions"> <input id="width" name="width" type="text" size="20"/> <input id="height" name="height" type="text" size="20"/> </div> </div>
Figure 5-2 shows the structure that arises when the browser transforms the HTML string into a DOM model. Since each node has attributes, we can represent the model in more detail, as shown in Figure 5-3. To learn more about the DOM, experiment with the tools mentioned in DOM Inspection (Chapter 18). You can point them at a web page to view the corresponding DOM model.
Each node has a number of standard attributes and operations. Those below let you inspect surrounding DOM context:
parentNode
The element’s parent node.
childNodes[]
An array of all immediate child nodes.
getElementsByTagName("tagName")
An array of all child nodes of type
tagName
(e.g.,h1
for all level-1 headings).
The structure can be manipulated as well as inspected. Following are the key manipulation methods available on all nodes (discussed further in Page Rearrangement):
appendChild(newChild)
Appends
newChild
.insertBefore(newChild, oldChild)
Inserts
newChild
just beforeoldChild
.removeChild(oldChild)
Removes
oldChild
.
You can also inspect the node itself; useful properties include:
id
The node’s ID.
nodeType
Usually 1 to denote a standard element. 2 represents a tag attribute and 3 represents a text node (explained later in this section).
tagName
The name of the tag (e.g., “h1”).
For a more complete set of properties and operations, a good source is available at HowToCreate.co.uk (http://www.howtocreate.co.uk/tutorials/index.php?tut=0&part=28).
Before using any of these properties, the first thing you need to do is get hold of a node somewhere in the DOM. Here’s how to grab a reference to an image node with an ID of “logo” buried anywhere inside the DOM:
var logo = document.getElementById("logo");
getElementById( )
will
recurse through the whole document to find the element with a given
ID property. Since calls like this are extremely commonplace, a
convenience function is used throughout the demos. The function name
is just one character, $
, which
leads to a neater syntax like this (as explained in Chapter 2):
var logo = $("logo");
Now that we have a reference to the image node, we can play
around with its properties. To start with, we can change its
src
property, which will cause it
to load a new image:
logo.src = "Images/web.png";
We can also set the image’s alt
text, which will show up on text
browsers, and as a tooltip on some browsers):
logo.alt = "The World Wide Web"
So far, we’ve altered a couple of image-specific properties. Each HTML element has its own set of properties that can be manipulated via DOM manipulation. There are references such as the w3schools web site (http://www.w3schools.com/htmldom/dom_reference.asp) that list the properties for each element.
Another common task is manipulating text, which in the DOM is
actually represented by special text nodes. This is a little
confusing as there’s no corresponding HTML tag. Look at the
following text, which seems to have some text freely roaming inside
a <p>
tag:
<p>Oh no! It's <strong>Mr. T</strong>!</p>
Text always resides inside. This is also a little confusing as
there’s no corresponding HTML tag; the text nodes are implicit. In
the example above, what the DOM holds is actually a paragraph node
<p>
with three children: a
text node, a strong node with a text node inside it, and another
text node. It’s represented in the DOM as follows:
<p> <textnode>Oh no! It's </textnode> <strong><textnode>Mr. T</textnode></strong> <textnode>!</textnode> </p>
Although the initial HTML is trivial, building up a message like this with DOM manipulation is somewhat complex:
message.appendChild(document.createTextNode("Oh no! It's ")); var strong = document.createElement("strong"); strong.appendChild(document.createTextNode("Mr. T")); message.appendChild(strong); message.appendChild(document.createTextNode("!"));
Even when text nodes aren’t involved, directly manipulating
the DOM can get quite ugly. A popular alternative is to use innerHTML
, which lets you specify an HTML
snippet. Thus, we can rewrite the code above by simply setting the
innerHTML
property:
message.innerHTML = "Oh no! It's <strong>Mr. T</strong>!";
By setting the element’s innerHTML
property, we’ve effectively
delegated the DOM manipulation to the web browser. It’s much simpler
and easier to understand than direct DOM manipulation and is
supported by all modern browsers. However, use it with caution,
because if you give it an invalid HTML string, you might end up with
a subtle bug related to an unexpected DOM state. Also, there are
some subtle portability issues, which mean that a particular HTML
segment won’t always produce quite the same DOM model. For instance,
certain kinds of whitespace will be captured as a text node by
Firefox, but are then ignored by IE (http://www.agavegroup.com/?p=32). So if you
use innerHTML
, be careful about
subsequent DOM manipulations on and around that content.
On the whole, direct DOM manipulation is often more
appropriate for complex operations. IE also offers an outerHTML
, less commonly used, which will
not only set the element’s contents, but also overwrite the element
itself. With outerHTML
, it’s as
if the element is replaced by the contents of its outerHTML
property.
If we’re looking at changing the display, a particularly
important aspect to consider is CSS style. CSS styles and classes
are just regular properties and can be manipulated just like the
others. Assume the stylesheet defines a CSS class called inYourFace
. When we change the message’s
className
property to inYourFace
, its display will automatically
update to reflect the inYourFace
definition.
[CSS] .inYourFace { padding: 10px; background-color: #ff4444; font-size: 250%; } [Javascript] message.className = "inYourFace";
As explained the next section, changing appearance by
switching classes is a good practice, but there are also times when
the JavaScript needs to manipulate style directly via the style
property present on all elements.
style
itself has a number of
properties, as you would see in a stylesheet, but with
JavaScript-ish camelCase (backgroundColor
) like that shown below, as
opposed to the CSS hyphenated style (background-color
):
message.style.padding= "10px"; message.style.backgroundColor = "#ff4444"; message.style.fontSize = "250%";
Decisions
Will you alter the display via classname or style?
For those morphings related to CSS properties, you
have a choice between manipulation via style
and className
. As a general rule of thumb,
use className
when you can and
use style
for special
situations. Using className
follows the principle of unobtrusive JavaScript, because it clears
away any mention of fonts, colors, and layout from the code. Thus,
the code logic is clearer and the presentation details are
encapsulated better inside the stylesheet.
So what are those special cases where style
should be
altered? Here are a few situations:
- Style depends on some variable
For example, an HTML histogram, where the height of each element is calculated according to the value it represents.
- Animation
Animations are a special case of the previous point, where styles usually vary according to elapsed time.
- Multiple variables
Sometimes each style property is to be tied to a particular variable; e.g., font reflects age, color reflects keyboard focus, and so on. There would be too many combinations to hold each as a separate style. You could, however, hold each as a separate class, since an element can have more than one class (its
class
property can be a whitespace-separated list of class names).- Prototyping
When in creative-coding mode, setting the style directly is sometimes easier than messing around with a separate stylesheet. But don’t forget to refactor later on!
What sort of properties will be used?
In modern browsers, the DOM is very powerful, so you
can change just about anything you can see. The following are some
of the properties that can be typically altered. Note that, as
just mentioned, many of the CSS styles will usually be altered
indirectly, via className
.
- Color—style.backgroundColor, style.fontColor
Change to a distinguishing color to highlight an entire image or control—for example, to draw the user’s attention or indicate that an item has been selected.
Change to a distinguishing color to highlight a range of text. This could be combined with a font color change to indicate that text has been selected.
Change to a symbolic color to denote some status change; e.g., “red” for stopped, and “green” for running.
Provide an animation effect by fading or brightening a control. This can draw attention more effectively than can a sudden change in the control itself.
Change color according to a variable; e.g., the brightness of a blog posting’s header varies according to the number of times it’s been viewed.
- Background Image—style.backgroundImage
Change the image to indicate the status of an element; e.g., a control might be stopped or started, or source code might be OK, have warnings, or have errors.
- Border Style—style.borderWidth, style.borderColor, style.borderColor
Highlight/Select an element to draw attention to it.
Indicate whether some text is editable or read-only.
- Font Style—stylefontSize, style.fontWeight, style.fontStyle
Change the size, weight, and/or slant of a font to highlight some text.
- Inner/Outer HTML—style.innerHTML, style.outerHTML
Change some text content.
Change arbitrary HTML, possibly changing the nested HTML of an element as well.
- Image Source—src
Change an image object by modifying
src
to point to another image URL.
Real-World Examples
Ajax-S
Robert Nyman’s Ajax-S (http://www.robertnyman.com/ajax-s/) is a slideshow manager—think “Powerpoint Lite”—that morphs the slide content as you flick through the presentation (Figure 5-4).
Digg Spy
Digg Spy (http://digg.com/spy) shows new stories and events such as user moderation as they happen (Figure 5-5). What’s interesting from a Display Morphing perspective is the fade effect used to highlight each item as it appears.
Ajax Spell Checker
The Ajax Spell Checker (http://www.broken-notebook.com/spell_checker/index.php) highlights any misspellings. It morphs the words into a red, underlined form that is familiar to any MS-Word user. A similar service is also offered by another Ajax App, Gmail (http://gmail.com).
Code Example: AjaxPatterns Countdown Demo
The example is a simple countdown demo (http://www.ajaxify.com/run/countdown/). A number counts down with the background color indicating “heat level”—green at the start, and transitioning to red at the end.
The div
that changes color
is called timeout
. Its initial
declaration simply specifies its ID and class:
<div id="timeout" class="status"></div>
Using techniques covered in the
Scheduling (Chapter 7) pattern, the startup
sequence arranges for onTick( )
to be called every 100 milliseconds. At each interval, timeout
is modified. innerHTML
changes to alter the text and
style.backgroundColor
changes to
alter the color:
$("timeout").innerHTML = Math.round(secsRemaining); $("timeout").style.backgroundColor = calculatePresentColor( );
The background color is determined by a function that manages
the transition from green to red according to secsRemaining
. Colors are usually
represented with hex RGB values—e.g., you can set style.backgroundColor
to the string,
#00ff00
, to make it green.
However, there’s a more convenient notation for this, based on
percentages, so an equivalent representation is rgb(0%,100%,0%)
. The algorithm here
gradually increases the red component from 0 percent to 100 percent
and decreases the green component from 100 percent to 0 percent.
Blue is fixed at 0 percent. At each moment, the red component is
assigned to the percentage of time remaining and the green component
to the percentage covered so far.
function calculatePresentColor( ) { var secsSoFar = TOTAL_SECS - secsRemaining; var percentSoFar = Math.round((secsSoFar / TOTAL_SECS) * 100); var percentRemaining = Math.round((secsRemaining / TOTAL_SECS) * 100); return "rgb(" + percentSoFar + "%, " + percentRemaining + "%, 0%)"; }
Related Patterns
Metaphor
Display Morphing is like using a magic paintbrush to change the appearance of anything on the page.
Want To Know More?
InnerHTML versus DOM Discussion between Alex Russell and Tim Scarfe (http://www.developer-x.com/content/innerhtml/default.html)
Page Rearrangement
⊙⊙⊙ Add, Adjust, Change, Delete, DOM, Move, Overlay, Rearrange, Restructure, Remove
Goal Story
Doc is looking at a X-ray image in an online diagnostics system. When the mouse rolls over the body, an “advice box” appears at the bottom of the page with stats to help interpret the image. In case the box gets in the way, he’s able to drag it around the page.
Problem
How can you dynamically restructure the page?
Forces
As the user’s task and context changes, Ajax Apps need to present different information.
Changes to page structure should be as smooth as possible.
Refreshing the page breaks continuity and also clears JavaScript state.
Refreshing the page only allows for a discrete transition from one appearance to another—it’s not possible to gradually fade or move an item.
Solution
Add, remove, move, and
overlay elements by manipulating the DOM. The DOM is a
hierarchical structure that defines how elements relate to one
another. By adjusting the DOM structure, you can adjust where
elements appear on the page. There are also critical CSS styles that
affect page structure—you can change these by manipulating DOM
elements’ style
attributes. Note:
an online demo (http://ajaxify.com/run/page) illustrates the
code concepts throughout this Solution and the code snippets loosely
follow from the demo.
Adding is normally achieved by
introducing a new element as a child of an existing
container element, such as body
or div
. For example, you can create a new
div
element and add it to a
parent div
:
var message = document.createElement("span"); $("container").appendChild(message);
Another way to insert an element is to append to the innerHTML
property of the container
element:
$("container").innerHTML += "<span></span>";
The opposite action is removing. By removing an element from the DOM, you take it off the display:
$("container").removeChild($("message"));
As a variant of adding and removing, you can keep an element
on the page but toggle its visibility using CSS. There are two
alternative style properties you can use: the visibility
style and the display
style. The former will always
preserve layout while the latter will cause the element to squeeze
in and out when it’s shown or hidden. So with no visibility
, it’s like the element’s still
there but wearing a coat of invisible paint. This makes layout easy,
because everything stays where it is, but it can be ugly to have a
big patch of whitespace on the page. With the display
style, it’s more like the
element’s really disappeared, so the other elements squeeze in to
take the place it had occupied. This makes layout a bit more
complex, but is usually better visually. The visibility
property alternates between
visible
and hidden
:
$("message").style.visibility = "visible"; // Now you see me $("message").style.visibility = "hidden"; // Now you don't ("Invisible paint")
display
is actually used
for more than just showing and hiding an element—it also defines how
an element appears when it is visible. Briefly,
the main options are block
(the
default for div
elements),
inline
(the default for span
elements), and none
(when it’s not to be shown).
$("message").style.display = "block"; // Now you see me, with block layout $("message").style.display = "inline"; // Now you see me, with inline layout $("message").style.display = "none"; // Now you don't ("Gone away")
You can move an element around in a couple of ways. First, you can remove it from one place in the DOM and add it to another:
container.removeChild(message); extraContainer.appendChild(message);
Second, you can adjust CSS properties. The most direct styles
are left, right, top
, and
bottom
, which define the
coordinates of the element in question:
message.style.top = "150px"; message.style.left = "50px";
But what is the (0,0) point these coordinates are relative to?
The precise meaning of these properties is modulated by the positioning
element.
If
static
, the coordinates have no effect—the element is positioned according to standard CSS layout rules.If
relative
, the coordinates are relative to the position it would normally be positioned under standard CSS layout rules—they suggest how far its displaced.If
absolute
, the coordinates are relative to the top-left of the entire document.If
fixed
, the coordinates are relative to the top-left of the browser viewport (the portion of the browser window that shows the page). Even if you scroll the document, the element will stick right on the same point on the screen as before.
Positioning is set with standard CSS styles:
message.style.left = "150px";
Finally, you can also overlay elements.
An HTML document has “2.5” dimensions, which is to say that it has a
limited notion of depth in which elements are able to overlap each
other. The critical CSS style here is zIndex
, which signifies an element’s
depth. When two elements occupy the same portion of the page, the
element that will appear in front is the one with the higher
zIndex
. The zIndex
is not a real depth, because all
that matters is the relative ordering of
zIndex
values. Against a zIndex
of zero, it makes no difference
whether an element’s zIndex
is 1,
10, or 100. The default value is 0, and a zIndex
can take on any positive or
negative value.
message.style.zIndex = -100; // Behind of all elements with default zIndex message.style.zIndex = 100; // In front of all elements with default zIndex
Decisions
Which positioning style to use?
A single application can combine different types of
positioning. In most cases, static
—the default positioning—suffices.
Nonstatic positioning is most commonly used with more
free-floating elements, such as Sprites
(Chapter 15) or elements
suitable for Drag-And-Drop (Chapter 15). For nonstatic
positioning, relative
positioning tends to be the most useful, because it allows you to
move the element around within a defined container.
How will you protect against memory leaks?
Continuously adding and removing elements leads to the risk of memory leaks. JavaScript is supposed to perform garbage collection, automatically removing variables that are no longer referenced. However, it’s sometimes buggy (notoriously in IE) and, in any event, you have to be sure that elements are really dereferenced when they’re no longer used. Some general guidelines are as follows:
- Avoid global variables where possible
Local variables go out of scope, so if a local variable points to a deleted element, the element will disappear. But if a global variable points to such an element, it will stick around.
- Explicitly nullify references
If you’re sure a variable will no longer need to use the value it references, set the variable to null.
- Avoid or destroy circular references
You can sometimes end up with a cyclic structure that no one’s using anymore, but that sticks in memory because garbage collection isn’t smart enough to remove it (it concludes each object is still relevant because at least one reference exists). This can happen, for instance, when an object’s event handler refers back to the object itself (see “Javascript Closures” [http://jibbering.com/faq/faq_notes/closures.html#clMem] for more details).
- Test, test, test.
It may not be fun, but you need to stress test your application under different browser environments, monitoring memory usage to ensure it’s stable. (See System Test [Chapter 19]).
Real-World Examples
TadaList
TadaList (http://tadalist.com; a screencast is available at http://www.tadalist.com/theater) allows users to manage TODO items (Figure 5-7). Each TODO item is a phrase, like “Finish homework,” and the user can add, remove, and rearrange items.
Super Maryo World
Super Maryo World (http://www.janis.or.jp/users/segabito/JavaScriptMaryo.html) is an outright Ajaxian video game, a clone of the classic Super Mario Bros game implemented with standard Ajax technologies (Figure 5-8). The manipulation of game characters and fixtures illustrates how elements can rapidly be added, removed, and moved around. It’s Page Rearrangement, real-time!
Kiko
Kiko (http://kiko.com) is a direct-manipulation Ajax calendar application. You can add and remove appointments, drag them around to change the time, and stretch them out to increase duration.
Code Example: AjaxPatterns Basic Wiki
The Basic Wiki Demo (http://ajaxify.com/run) periodically polls
for a fresh list of messages to display. Each time the list is
retrieved, it removes all existing messages and adds the new list.
The containing element is called messages
, and removing all messages
involves running a loop across each of its children:
while ($("messages").hasChildNodes( )) { $("messages").removeChild($("messages").firstChild); }
Each message is used to create a new textarea
element (among other things),
which is then added to a new div
,
which in turn is added to the messages
container:
for (var i=0; i<wikiMessages.length; i++) { var messageArea = document.createElement("textarea"); ... messageDiv = document.createElement("div"); ... messageDiv.appendChild(messageArea); ... $("messages").appendChild(messageDiv); ... }
Related Patterns
Get Ajax Design Patterns now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.