DHTML is HTML that includes JavaScript code that changes the content or layout of the page in the browser without going back to the server. When you write an HTML page, you're actually writing an object tree. All the tags -- the little <table>s and <p>s -- become objects in the JavaScript space. Using JavaScript, you can change their content, Cascading Style Sheets (CSS) styling, and location -- all without ever going back to the server. DHTML is the intersection of HTML, CSS, and JavaScript.
The technology to use DHTML has been around since the addition of JavaScript code to the browser, but support for it has been spotty. Microsoft® Internet Explorer did an excellent job early on, but during the same period, Netscape V4 lacked support. Recently, Mozilla and Firefox have stepped up with solid DHTML support, and some believe the pendulum has swung so far that Internet Explorer is now behind the curve.
Incorporating JavaScript into your page makes the page dynamic and creates a more compelling user experience. Users can get more data more quickly, look at information from different aspects, and seamlessly navigate the site -- and the site doesn't have to go back to the server for lots of pages.
However, there's also a reason to avoid using JavaScript: browser compatibility. In the early days of flat HTML, Internet Explorer rendered pages differently from Netscape. Those problems were fixed, but when support for CSS was added, new compatibility issues arose. Now most of the CSS issues have been solved, but JavaScript compatibility issues have cropped up.
These compatibility problems have no easy solution. You need to weigh the benefit of what the JavaScript is doing against the number of browsers you'll need to test against and support.
Here are some recommendations:
- Keep the JavaScript as simple as you can.
- When you find a compatibility problem, check the Web to determine the best fix. You'll often find well-researched answers.
- Have a plain HTML fallback for browsers you don't support.
- Keep a list of the browsers (and their version numbers) you support.
- Check Internet Explorer on Mac and Microsoft Windows®.
The examples here are as simple and clear as possible. However, they've only been tested on Firefox, so some compatibility issues arise. The article alerts you to compatibility problems so you can learn from the experience.
The easiest way to implement DHTML is to first write it in a plain HTML page, without PHP or any server-side language, then use that code as a template for the PHP code to generate the DHTML. That way, you can check the basics of the solution and separate problems on the server from problems on the client. In each example, this article begins by showing the HTML, then shows this code used with PHP.
The first example is simple and compatible: a floating ad box. Figure 1 shows an ad box with a close button, sitting on top of a page.
Figure 1. The floating ad box
The user can click the Close button to dismiss the ad box, as shown in Figure 2.
Figure 2. The page after the ad box is dismissed
Listing 1 shows the code for the page.
Listing 1. The ad box code
<html>
<head>
<title>Ad box demonstration</title>
<style>
body {
width: 800px;
}
.ad-box {
background: #eee;
border: 1px solid black;
padding: 5px;
position: absolute;
left: 50px;
top: 50px;
width: 600px;
}
.ad-box-title {
background: #ccc;
padding: 5px;
font-weight: bold;
font-size: large;
text-align: center;
text-transform: uppercase;
letter-spacing: 0.2em;
}
</style>
<script>
function closead()
{
var obj = document.getElementById( "ad" );
obj.style.visibility = "hidden";
}
</script>
</head>
<body>
<div class="ad-box" id="ad">
<div class="ad-box-title">
Special offer
</div>
<p>
You have been selected for our special offer. Can you imagine? What
are the odds? Just buy five hundred or more of our product and we
will give you a 1% discount on additional orders. Only the Department
of Defense gets deals like that!
</p>
<p style="text-align: right;">
<a href="javascript:closead();">close</a>
</p>
</div>
<p>This is our home page. Welcome to it. Here we talk about all of
our great products.</p>
<h1>Products</h1>
<p>This is a list of our products:</p>
<ul>
<li>The amazing all in one toothpix holder and axe grinder.</li>
<li>The complete Jean Claude Van Damme DVD collection.</li>
</ul>
</body>
</html>
|
Most of the work in this example is done using CSS. Using the position CSS attribute, the box is above the page text. The left and top attributes position the element with respect to the upper-left corner of the page.
To make the box disappear, you put some JavaScript code behind the close anchor tag. Instead of using an http protocol, you use the javascript protocol and invoke the closead function. The closead JavaScript function gets the object reference to the ad <div> and sets the CSS style visibility from visible to hidden, and voila: The ad box vanishes.
This example shows a few fundamentals of DHTML. The first is the use of id attributes. To reference an item dynamically, give it a unique ID via the id attribute. In this case, you give the ad box the ID ad. Then you use the document.getElementById method to get a reference to the <div> object for the ad.
This example also demonstrates the javascript pseudo-URL you can use in anchor tags. Using this, you can invoke JavaScript anywhere you put a link.
These techniques are compatible with other browsers. All modern browsers support JavaScript, CSS, absolute positioning, and the setting of dynamic elements like the visibility attribute at runtime.
Now that you have the ad box code in DHTML, you can see how to implement it in PHP (see Listing 2). The only choice with the ad box is whether it should be shown. The PHP code writes out the ad box with a single function call, which the page can call or not call depending on the circumstances.
Listing 2. The ad box in PHP
<?php
function place_ad( $title )
{
?>
<div class="ad-box" id="ad">
<div class="ad-box-title">
<?php echo( $title ); ?>
</div>
<p>
You have been selected for our special offer. Can you imagine? What
are the odds? Just buy five hundred or more of our product and we
will give you a 1% discount on additional orders. Only the Dept.
of Defense gets deals like that!
</p>
<p style="text-align: right;">
<a href="javascript:closead();">close</a>
</p>
</div>
<?php
}
?>
<html>
<head>
<title>Ad box demonstration</title>
<style>
body {
width: 800px;
}
.ad-box {
background: #eee;
border: 1px solid black;
padding: 5px;
position: absolute;
left: 50px;
top: 50px;
width: 600px;
}
.ad-box-title {
background: #ccc;
padding: 5px;
font-weight: bold;
font-size: large;
text-align: center;
text-transform: uppercase;
letter-spacing: 0.2em;
}
</style>
<script>
function closead()
{
var obj = document.getElementById( "ad" );
obj.style.visibility = "hidden";
}
</script>
</head>
<body>
<?php place_ad( "Today's Special Offer!" ); ?>
<p>This is our home page. Welcome to it. Here we talk about all of
our great products.</p>
<h1>Products</h1>
<p>This is a list of our products:</p>
<ul>
<li>The amazing all in one toothpix holder and axe grinder.</li>
<li>The complete Jean Claude Van Damme DVD collection.</li>
</ul>
</body>
</html>
|
The new place_ad PHP function puts the ad onto the page with a flexible title string. To be able to put ads on multiple pages, you can take out this function and put it in a separate file you include when you need to insert an ad on the page.
Another variant on the floating box theme is the pop-up. You can use a pop-up to bring up additional information when the user requests it. Consider the text shown in Figure 3.
Figure 3. A plain text page about animals
Wouldn't it be great if the user could see more information about a giraffe? The text includes a handy link: If you click it, you see the pop-up shown in Figure 4.
Figure 4. More information about giraffes
In this case, you bring up the box on request and position it relative to text in the document. The code is shown in Listing 3.
Listing 3. The pop-up box HTML
<html>
<head>
<title>Pop up Example</title>
<style type="text/css">
body { font-family: arial, verdana, sans serif; }
#popup {
position: absolute;
padding: 5px;
border: 1px solid black;
background: #eee;
left: 0px;
top: 0px;
visibility: hidden;
}
</style>
<script>
function popup( id )
{
var obj = document.getElementById( id );
var popup = document.getElementById( 'popup' );
if ( popup.style.visibility == 'visible' )
{
popup.style.visibility = 'hidden';
}
else
{
popup.style.left = obj.offsetLeft + "px";
popup.style.top = ( obj.offsetTop + 20 ) + "px";
popup.style.visibility = 'visible';
}
}
</script>
</head>
<body>
<div id="popup">
An animal with a very long neck.
</div>
<h2>Animals</h2>
A <a href="javascript:popup('word')" id="word">giraffe</a>
is a very interesting animal.
</body>
</html>
|
In this example, the popup <div> that contains the pop-up information begins defined as hidden and absolutely positioned at top left on the page. Then the anchor tag around the word giraffe calls the JavaScript to bring up the pop-up.
The popup JavaScript method finds the text of giraffe using the word ID. Next, it uses the offsetLeft and offsetTop values to position the pop-up just below the text. If the pop-up is already visible, it's hidden.
You can set the contents of the pop-up before making it visible by setting the innerHTML member on the popup <div> object.
This code is fairly compatible. However, issues may occur due to differences in the values of offsetLeft and offsetTop from browser to browser, particularly in pages with complex nested content. To get a more accurate position reading in a complex page, you may need to recursively add the offsetLeft and offsetTop values, using the offsetParent object to get each object's parent as you move up the HTML object tree.
With the DHTML code in hand, you can now make it easier to use pop-ups in PHP applications. The PHP code is shown in Listing 4.
Listing 4. The pop-up box PHP code
<?php
function popup_header()
{
?>
<style type="text/css">
body { font-family: arial, verdana, sans serif; }
#popup {
position: absolute;
padding: 5px;
border: 1px solid black;
background: #eee;
left: 0px;
top: 0px;
visibility: hidden;
}
</style>
<script>
function popup( id, info )
{
var obj = document.getElementById( id );
var popup = document.getElementById( 'popup' );
if ( popup.style.visibility == 'visible' )
{
popup.style.visibility = 'hidden';
}
else
{
popup.innerHTML = info;
popup.style.left = obj.offsetLeft + "px";
popup.style.top = ( obj.offsetTop + 20 ) + "px";
popup.style.visibility = 'visible';
}
}
</script>
<?php
}
function popup( $id, $text, $info )
{
?>
<a href="javascript:popup('<?php echo($id) ?>','<?php echo($info) ?>')"
id="<?php echo($id) ?>"><?php echo($text) ?></a>
<?php
}
?>
<html>
<head>
<title>Pop up Example</title>
<?php popup_header(); ?>
</head>
<body>
<div id="popup">
</div>
<h2>Animals</h2>
A
<?php popup( 'word', 'giraffe',
'An animal with a very long neck.' ) ?>
is a very interesting animal.
</body>
</html>
tml>
|
In this case, you separate the generation of the header and the placement of each pop-up. A page must call popup_header inside the head tag, then add a <div> tag with the id value popup. Then, the page calls the PHP popup function wherever a pop-up is required.
This PHP popup function takes three parameters: the pop-up's ID, the plain-text version, and the text to pop up when the item is clicked. The function then renders an anchor tag that looks almost exactly like the original DHTML version.
Because multiple pop-ups will probably appear on a single page, the third parameter is added to provide the text that appears in the pop-up. Implement this by adding an additional parameter to the popup JavaScript function. The innerHTML of the <div> tag is then set to the contents of this new parameter.
Another option for hiding and showing data on a page is the spinner. In this model, the page is broken into sections that can be hidden or shown individually via the use of a spinner. Figure 5 shows a page with two spinner sections; each section is initially closed.
Figure 5. The sections of the page with the spinners closed
Clicking the Open link in the Level One section displays the content in that section, as shown in Figure 6.
Figure 6. The page with the first spinner open
You can use graphics instead of the words open and closed. By convention, these graphics are usually triangles that point to the right for closed or down for open; or plus and minus signs, where plus means closed and minus means open. (Opinions about which approach is better come down to the Mac vs. Windows debate. The spinners on the two platforms correspond to the two different options.)
The code in Listing 5 shows how these spinners work:
Listing 5. The spinner HTML
<html>
<head>
<title>Spinner Example</title>
<style type="text/css">
body { font-family: arial, verdana, sans serif; width: 800px; }
.item-header a { font-size: small; }
.item-header {
font-weight: bold; border-bottom: 1px solid black;
font-size: x-large;
}
.item-body {
margin: 0px; font-size: small;
visibility: hidden; height: 0px;
}
</style>
<script>
function spin( obj )
{
var spinner = document.getElementById( obj );
var spinner_content = document.getElementById( obj+"_body" );
if ( spinner_content.style.visibility == 'visible' )
{
spinner.innerHTML = 'open';
spinner_content.style.visibility = 'hidden';
spinner_content.style.height = '0px';
spinner_content.style.margin = '0px';
}
else
{
spinner.innerHTML = 'close';
spinner_content.style.visibility = 'visible';
spinner_content.style.height = 'auto';
spinner_content.style.margin = '20px 0px 20px 50px';
}
}
</script>
</head>
<body>
<div class="item-header">
<a href="javascript:spin('lev1')" id="lev1">open</a> Level One
</div>
<div class="item-body" id="lev1_body">
This is the content of level one.
</div>
<div class="item-header">
<a href="javascript:spin('lev2')" id="lev2">open</a> Level Two
</div>
<div class="item-body" id="lev2_body">
This is the content of level two.
</div>
</body>
</html>
|
Two groups of <div>s define each section and its content. By convention, the header is given the ID lev, plus a number (for example, lev2), and the body is given the same ID with postfix _body. lev2 is the link for the spinner, and lev2_body is the body of that item.
The brains of the spinner are implemented in the spin function, which looks at the visibility of the body of the spinner and inverts it, changing visible to hidden, and hidden to visible.
You set the height attribute to 0px when the item is invisible and auto when it's visible. With Internet Explorer, when an item is invisible, the space around it collapses. But with Firefox, when content is invisible, the space remains as a placeholder. You need to set height to 0px to make the space collapse properly.
To use graphics instead of text to express the open and closed states, alter the code that changes the innerHTML value of the spinner object to specify an image tag instead of text.
To implement spinners in PHP, you follow a standard pattern for server-side creation of DHTML code, bracketing segments of HTML in start and end functions. The PHP code is shown in Listing 6.
Listing 6. The spinner PHP
<?php
function start_spinner( $id, $title )
{
?>
<div class="item-header">
<a href="javascript:spin('<?php echo( $id ); ?>')"
id="<?php echo( $id ); ?>">open</a> <?php echo( $title ); ?>
</div>
<div class="item-body" id="<?php echo( $id ); ?>_body">
<?php
}
function end_spinner()
{
?>
</div>
<?php
}
?>
<html>
<head>
<title>Spinner Example</title>
<style type="text/css">
body { font-family: arial, verdana, sans serif; width: 800px; }
.item-header a { font-size: small; }
.item-header {
font-weight: bold; border-bottom: 1px solid black;
font-size: x-large;
}
.item-body {
margin: 0px; font-size: small;
visibility: hidden; height: 0px;
}
</style>
<script>
function spin( obj )
{
var spinner = document.getElementById( obj );
var spinner_content = document.getElementById( obj+"_body" );
if ( spinner_content.style.visibility == 'visible' )
{
spinner.innerHTML = 'open';
spinner_content.style.visibility = 'hidden';
spinner_content.style.height = '0px';
spinner_content.style.margin = '0px';
}
else
{
spinner.innerHTML = 'close';
spinner_content.style.visibility = 'visible';
spinner_content.style.height = 'auto';
spinner_content.style.margin = '20px 0px 20px 50px';
}
}
</script>
</head>
<body>
<?php start_spinner( 'lev1', "Level One" ); ?>
This is the content of level one.
<?php end_spinner( ); ?>
<?php start_spinner( 'lev2', "Level Two" ); ?>
This is the content of level two.
<?php end_spinner( ); ?>
</body>
</html>
|
Each section of content is bracketed with calls to start_spinner and end_spinner. The start_spinner function takes two arguments: the ID of the spinner and its title. The end_spinner call finishes the <div> tag opened in start_spinner to hold the content of the spinner.
You could put the content of the spinner as a third argument. But often, these sections of content are involved and the interface would be painful to use if you wrote it that way. This pattern of bracketing dynamic sections in start and end functions means that the PHP in between can be as complex as you like.
Tabs are another common way to view different portions of content. A lightweight version of tabs appears on the MetaCritic site (see Resources), which uses tabs to switch between viewing a list of games based on the name or based on the reviewed score, without going back to the server. Figure 7 shows a simplified example of this game list.
Figure 7. The games sorted by name
To see the games sorted by review score, click the By Score link. The list changes, as shown in Figure 8.
Figure 8. The games sorted by score
Instead of doing sophisticated sorting on the client side, the MetaCritic site uses the equivalent of flash cards: One card has the list sorted by name, the other by score. Clicking the links hides one card and shows another. The code is shown in Listing 7.
Listing 7. The tabs HTML
<html>
<head>
<title>Tabs Example</title>
<style type="text/css">
body { font-family: arial,verdana,sans serif; }
.button-on, .button-off { padding: 3px; border: 1px solid black; }
.button-on { background: #333; color: white; font-weight: bold; }
.game-list { position: absolute; top: 0px; left: 0px; }
.container { padding: 5px; border: 1px solid black; margin: 5px;
position: relative; height: 400px; width: 200px; }
</style>
<script>
function show( divid )
{
var tos = [ "names", "score" ];
for( var t in tos )
{
var to = document.getElementById( tos[t] );
to.style.visibility = "hidden";
to.style.height = "0px";
var bo = document.getElementById( tos[t]+"-button" );
bo.className = "button-off"
}
var to = document.getElementById( divid );
to.style.visibility = "visible";
to.style.height = "auto";
var bo = document.getElementById( divid + "-button" );
bo.className = "button-on";
}
</script>
</head>
<body onload="show('names')">
Sort by:
<a href="javascript:show('names')" id="names-button"
class="button-on">By Name</a>
<a href="javascript:show('score')" id="score-button"
class="button-off">By Score</a><br/>
<div class="container">
<div id="names" class="game-list">
<table width="100%">
<tr><td>Crank Shaft</td><td>22</td></tr>
<tr><td>Driver</td><td>42</td></tr>
<tr><td>Football 2006</td><td>72</td></tr>
<tr><td>Soccer 2006</td><td>99</td></tr>
<tr><td>Xevious</td><td>32</td></tr>
</table>
</div>
<div id="score" class="game-list">
<table width="100%">
<tr><td>Crank Shaft</td><td>22</td></tr>
<tr><td>Xevious</td><td>32</td></tr>
<tr><td>Driver</td><td>42</td></tr>
<tr><td>Football 2006</td><td>72</td></tr>
<tr><td>Soccer 2006</td><td>99</td></tr>
</table>
</div>
</div>
</body>
</html>
|
The two lists are in two <div> tags: names and score. The names <div> has the list of games sorted by name, and score has them sorted by score. The show function connected to the By Name and By Score links first makes all the lists invisible, then makes the selected list visible.
Some other interesting things are going on. First, you change the CSS class of the links dynamically using the className attribute. That's how the Selected button changes from white to black.
Second, look at the CSS for names and score. Both <div>s are absolutely positioned at upper left, but they aren't in the upper-left corner of the page. That's because they're in the <div> with the ID value container, which is given a position of relative. That container <div> effectively resets the origin points of the items contained within it. The tables are positioned relative to the container, not to the page.
This type of DHTML, in addition to being reasonably complex and interesting, is compatible with almost every modern browser.
Another tool in developing DHTML in PHP is the use of output buffering. Output buffering stores the text, tags, and echo material from the page and returns it as a string for later use. To implement the tabs, you use output buffering to store the content of the tab as a string before rendering into the page (see Listing 8).
Listing 8. The tabs PHP
<?php
$tabs = array();
$current_tab = null;
function start_tab( $id, $title )
{
global $tabs, $current_tab;
ob_start();
$current_tab = $id;
$tabs[ $id ] = array( 'title' => $title, 'html' => "" );
}
function end_tab()
{
global $tabs, $current_tab;
$tabs[ $current_tab ][ 'html' ] = ob_get_contents();
ob_end_clean();
}
function get_tab_ids()
{
global $tabs;
$ids = array();
foreach( $tabs as $tabid => $tab ) {
$ids []= "'".$tabid."'";
}
return $ids;
}
function get_first_tab()
{
$tabs = get_tab_ids();
return $tabs[0];
}
function place_tab_buttons()
{
global $tabs;
foreach( $tabs as $tabid => $tab ) {
?>
<a href="javascript:show('<?php echo($tabid); ?>')"
id="<?php echo($tabid); ?>-button"
class="button-off"><?php echo( $tab['title'] ); ?></a>
<?php
}
}
function place_tab_content()
{
global $tabs;
foreach( $tabs as $tabid => $tab ) {
?>
<div id="<?php echo($tabid); ?>" class="game-list">
<?php echo( $tab['html'] ); ?>
</div>
<?php
}
}
?>
<?php start_tab( 'names', "By Name" ); ?>
<table width="100%">
<tr><td>Crank Shaft</td><td>22</td></tr>
<tr><td>Driver</td><td>42</td></tr>
<tr><td>Football 2006</td><td>72</td></tr>
<tr><td>Soccer 2006</td><td>99</td></tr>
<tr><td>Xevious</td><td>32</td></tr>
</table>
<?php end_tab( ); ?>
<?php start_tab( 'scores', "By Score" ); ?>
<table width="100%">
<tr><td>Crank Shaft</td><td>22</td></tr>
<tr><td>Xevious</td><td>32</td></tr>
<tr><td>Driver</td><td>42</td></tr>
<tr><td>Football 2006</td><td>72</td></tr>
<tr><td>Soccer 2006</td><td>99</td></tr>
</table>
<?php end_tab( ); ?>
<html>
<head>
<title>Tabs Example</title>
<style type="text/css">
body { font-family: arial,verdana,sans serif; }
.button-on, .button-off { padding: 3px; border: 1px solid black; }
.button-on { background: #333; color: white; font-weight: bold; }
.game-list { position: absolute; top: 0px; left: 0px; }
.container { padding: 5px; border: 1px solid black; margin: 5px;
position: relative; height: 400px; width: 200px; }
</style>
<script>
function show( divid )
{
var tos = [ <?php echo( join( ",", get_tab_ids() ) ); ?> ];
for( var t in tos )
{
var to = document.getElementById( tos[t] );
to.style.visibility = "hidden";
to.style.height = "0px";
var bo = document.getElementById( tos[t]+"-button" );
bo.className = "button-off"
}
var to = document.getElementById( divid );
to.style.visibility = "visible";
to.style.height = "auto";
var bo = document.getElementById( divid + "-button" );
bo.className = "button-on";
}
</script>
</head>
<body onload="show(<?php echo( get_first_tab() ); ?>)">
Sort by:
<?php place_tab_buttons() ?>
<div class="container">
<?php place_tab_content() ?>
</div>
</body>
</html>
|
The PHP code starts by defining two variables: tabs and current_tab. The tabs array holds the id, title, and html values of each tab. current_tab points to the tab being created between the calls to start_tab and end_tab. The start_tab function takes two parameters: the id value of the tab and the title value. It then begins the output buffering.
The end_tab function stops the output buffering and stores the resulting HTML in the html value in the current_tab of the tabs array.
Looking down the page, the calls to start_tab and end_tab are visible around the tab content to be rendered onto the tab.
The important functions for rendering the tabs onto the page are place_tab_buttons and place_tab_content. The place_tab_buttons function creates anchor tags that have the name of the tab and that switch tabs when clicked. The place_tab_content function creates the <div> tags with the content of each tab stored by output buffering.
get_tab_ids and get_first_tab are helper functions used in creating the JavaScript. They return the complete list of tab IDs and the ID of the first tab, respectively.
The next article in this "Devise Web 2.0 applications with PHP and DHTML" series covers generating dynamic graphs with JavaScript. It will teach how to create new HTML elements on the fly and position them around the pages presented by your PHP application.
Learn
-
PHP.net is the starting point for all things PHP.
-
The Ajaxian blog is a great place to go when looking for client-side coding tips.
-
Ajax Freaks is another good client-side site.
-
WebReference.com has good articles and reference materials on browser compatibility issues.
-
O'Reilly's Dynamic HTML: The Definitive Reference is the best book on Dynamic HTML.
-
To see an example of the sample tabs discussed in this article, visit Metacritic.com.
-
Read the developerWorks article "A cross-browser DHTML table" to learn how to create a cross-browser table with DHTML and JavaScript, which does most of the things that a table component written in Java might do.
-
Read "Build apps using Asynchronous JavaScript with XML (AJAX)" to learn how to build AJAX-based Web applications, complete with real-time validation and without page refreshes, by following the construction of a sample book order application.
-
Visit IBM developerWorks' PHP project resources to learn more about PHP.
-
Stay current with developerWorks technical events and webcasts.
-
Visit the developerWorks Open source zone for extensive how-to information, tools, and project updates to help you develop with open source technologies and use them with IBM's products.
Get products and technologies
-
Innovate your next open source development project with IBM trial software, available for download or on DVD.
Discuss
-
Get involved in the developerWorks community by participating in developerWorks blogs.




