News Flash

Really excited about Twitter's new @anywhere platform. We'll be talking a lot about this at @chirp next month: http://bit.ly/dmBydU

Tagged: DOM

6 June 2008

Introduction

In this article I will explore the DOM, look at some common kinds of errors that are found in the DOM and how different debugging tools can be used to find such DOM errors and make sure that the DOM is interpreted consistently across browsers. Along the way, I’ll introduce Opera Dragonfly, the new kid on the web development debugging block, and show how it performs.

What is the DOM?

When a browser receives HTML code from a website, it creates a structured overview of the HTML document. This overview is known as the DOM, and through its structure JavaScript can access the various parts of the document.

The “parent” of the DOM is the document object, and using its methods and properties you can reach every part of the HTML file — for example by getting the first div with document.getElementsByTagName('div')[0] or the first element in the document with document.firstChild.

The DOM is usually visualised as a tree-like structure, as seen in Figure 1. The document itself should have only one child — typically the html element — and that element usually has the two children head and body. Each of those can have several children, so comparing this structure to the branches of a tree is a good way to understand it.

The DOM structure of a simple HTML document as a tree

Figure 1: The DOM structure of a simple HTML document, as viewed in the DOM viewer of Opera Dragonfly as a tree structure.

When constructing the DOM, the quality of the markup is very important. If the markup is not valid — for example if elements are not closed correctly — it’s much harder to create a tree structure from it. If, say, a document contains the markup <html><head><body></body></html> — what is the browser going to do? Make body a child of head? Since the head contents are not visible in user agents, this might make the entire document disappear!

Because invalid markup is so common, browsers have to add lots of special rules for creating DOM structures from invalid markup. These rules differ somewhat between browsers, which means that using invalid markup dramatically increases the risk that your site will appear differently in different browsers — or even break completely in some of them.

Common markup / DOM errors

Below I will look at some commonly encountered DOM errors, and how browsers interpret the markup.

Bad closing and nesting of elements

If you look at the first example file — df-dom-demo-unclosed-b.htm — you will see that the markup is invalid — there are multiple problems with it, in terms of unclosed elements and incorrect nesting. If you look at the generated DOM in various web page debugger tools (see Figure 2), you will see that browsers interpret this HTML very differently.

example invalid dom interpreted by Opera Dragonfly
example invalid dom interpreted by firebug
example invalid dom interpreted by IE developer toolbar

Figure 2: From top to bottom, this shows our incorrectly closed/nested example DOM as represented in Opera Dragonfly, Mozilla Firebug, and the Internet Explorer developer toolbar.

The DOM inspectors reveal that these browsers handle markup errors in very different ways: Opera makes the subsequent elements children of the b that lacks a closing tag. Firefox adds extra b elements between the p tags that were not present in the markup. In IE’s DOM we see that the text “This text should be a link” in fact appears to be outside the a tag that creates the link.

Because there is no standard way to handle invalid markup, none of these browsers are doing anything particularly wrong here. However, if you start adding extra styling like display:none to any of these elements or try cloning and manipulating them through the DOM, you’ get unexpected results across browsers because of the way the invalid markup is parsed into different DOM structures.

body inside the head

In the second sample file — df-dom-demo-head-contents.htm — you can see another example of invalid markup. There is a div element inside the head section. Check out this demo in various DOM explorers and you will see that all browsers move the div element out of the head and into the body. Figure 3 shows the different ways in which this DOM is interpreted by different browsers:

example invalid dom interpreted by Opera Dragonfly
example invalid dom interpreted by firebug
example invalid dom interpreted by IE developer toolbar

Figure 3: From top to bottom, this shows our content in the head example’s DOM as represented in Opera Dragonfly, Mozilla Firebug, and the Internet Explorer developer toolbar.

In Opera and IE, this means that any head content after the div also is moved outside of the head section. Also note the way Firefox has moved the style element from the body into the head. style elements are not allowed inside the body of course, so this is a logical thing to do, but it might confuse your script if it goes looking for the style element in a certain place in the tree.

Using DOM inspectors for debugging

As you have seen above, using DOM inspectors is a great way to see how browsers interpret the markup. This view can help explain issues you may run into when using the DOM via a JavaScript application. Some of the following examples are rather advanced, and it helps to have several of the DOM inspectors mentioned installed so that you can test the demos by yourself. The DOM inspectors used for the below analysis are Opera Dragonfly, Mozilla Firebug, the Webkit (Safari) Web Inspector, and the IE Web Developer Toolbar.

The third example file — df-dom-demo-app-meta.htm — is created by a rather unlucky web developer who has made several assumptions about how the DOM works. It turns out that he is wrong on most counts, and the app causes issues in most browsers! To start with, it doesn’t include a proper doctype in the first line. It then goes on to include a custom test element and a meta element with a custom http-equiv attribute value inside the head. Lastly, it includes a script inside the body that tries to read attribute values from both the meta and the test elements.

  • In Opera it generates two exceptions. Using the Opera Dragonfly DOM explorer (see Figure 4) you can see that the reason is that the test and meta elements are not inside the head. When Opera sees the test element, it thinks the custom element name indicates the page content start, so it closes the head section and traps both elements outside it.

    the debugging example in dragonfly

    Figure 4: Opera Dragonfly shows that Opera has trapped the two rogue elements outside the head. Hence, when the script looks for them inside the head section it can’t find them and throws exceptions.

  • In Firefox (see Figure 5), you will get the odd output message “name of bar attribute is _moz-userdefined” and sure enough, with Firebug’s DOM explorer you will see that Firefox added a _moz-userdefined attribute to the test element.

    the debugging example in firebug

    Figure 5: Firebug shows that Firefox has returned an error message and added an attribute to mark the test element as undefined in HTML. Since the script reads the first attribute from the element, it sees the _moz-userdefined attribute instead of what we expected.

  • In WebKit you get one exception (see Figure 6). Like Opera, it moves the test element out of head. Unlike Opera, it appends the element to body, and it doesn’t move the meta element.

    the debugging example in webkit

    Figure 6: WebKit shows that Safari has moved the test element into the body.

  • In IE7 the output is “Name of bar attribute is language. Value of meta attribute is null” — there are no exceptions reported, and neither result is what was expected. The IE Developer Toolbar’s DOM view is not quite powerful enough to explain these results — there are two IE bugs at play here: one is that IE exposes internal generic attributes in the Element.attributes collection, second is that weirdly enough the meta http-equiv attribute simply disappears. See Figure 7 for the output in the IE web developer toolbar.

    the debugging example in the IE web developer toolbar

    Figure 7: The IE web developer toolbar does not quite show us the DOM the scripts are working on. It appears to show that IE has preserved the meta http-equiv attribute, yet when we try reading it with the DOM method getAttribute() we get nothing.

Summary

Our unlucky developer has a few things to fix to get this example working properly cross-browser, but in this example it’s not hard to do so. He’ll need to validate the markup, get rid of the custom elements (and avoid these in the future, particularly inside the head as browsers are very picky about what they allow to appear there), and not rely on attribute order when reading attributes. You can see from the examples shown above that valid code is perhaps the most important factor for cross-browser compatibility on the DOM level, and that DOM inspectors help you understand how your scripts can access the document and how the markup is interpreted.

Continue reading 2
CSS3 Online Conference March 22nd 2010 banner ad

22 March 2007

There are some things in the world of accessibility that appear, on the face of it, to be really wonderful ideas… until you scratch slightly below the service. What may seem feasible when putting together some guidelines on accessibility might not ultimately translate well to a real-world application. Hands up who can remember the last time they felt compelled to use a longdesc attribute? And what about the accesskey attribute? Oh, you have used them you say. OK, let’s back up a little and find out what went wrong here.

The Problem With Accesskeys

The accesskey attribute is a neat little feature that is supposed to enable the user to quickly access a navigation, search, or some other feature of a page quickly. For example, I could use an accesskey of ’s’ for search on a site – on all sites that I might run – and have that as the trigger. Depending on the browser or other assistive technology (AT) used, that might involve the user hitting CTRL+s to activate it, or maybe ALT+S, or perhaps some other configuration.

However, those very same commands might be the same as existing commands in the browser or AT. So which command takes precedence? There is another factor to this ‘key already assigned’ problem, and that’s one of localization. While you may think that your browser has a spare key that you could use, in many other languages this won’t be the case.

Another problem with the accesskey attribute is how do you let the user know about it? As with the longdesc attribute, the availability of the attribute is not really shouted from the rooftops (with the exception of the little-used iCab browser). Unless you make a point of informing the user of accesskeys being used on the site in a separate page, perhaps backed up by judicious use of the title attribute for the relevant links, the user may never know that you made them available.

So it seems that, while accesskeys are a great idea, they are largely hidden and also cause their own set of problems because of the potential overlap with other technology. But wait, maybe there’s life in the old dog just yet…

Let the User Decide

I have an idea, or rather I had an idea, about the way it might work. I actually used this approach a couple of years ago when I re-built accessify.com, providing a method for the user to set their own accesskeys. They are not forced upon anyone – it’s a total opt-in. But in all honesty, it was more of an experiment on a live site than anything else, and it’s not transferable to other sites unfortunately. The solution was built using PHP, but I always thought that it could be converted to a JavaScript solution. It just took me a couple of years to get around to it (and documenting it!).

The JavaScript Approach

In the JavaScript solution, the end result is exactly the same as the version used on Accessify, just the mechanics have changed. Rather than go into all the lines of code and explain everything line-by-line, I’ll summarise how it works in plain old English.

  • There are a number of links on the example page (the links that we offer up as potential accesskey candidates) By default, they have no accesskey assigned, nor any title attribute:
    <h3>Navigation</h3>
    <ul>
    <li><a href="/" id="homelink">Home</a></li>
    <li><a href="/search/" id="searchlink">Search</a></li>

    </ul>
  • There’s a warning on the page that says "you need Javascript to set your own accesskeys" – this is set to display on screen by default
    Finally, there’s a form that allows you to set the characters you want for the related sections on the page. This is hidden by default (using CSS)
    <p id="warning">Cookies are used to save these preferences, but you have either disabled JavaScript, set your browser not to accept cookies, or your browser supports neither. The site will work perfectly well without these preference options.</p>

    #warning
    {
    color:red;
    }

    function hideWarning()
    {
    var elwarn = document.getElementById(’warning’);
    elwarn.style.display="none";

    var elsetter = document.getElementById(’setter’);
    elsetter.style.display="block";
    }

If the user gets to the page with JavaScript disabled (or with the scripts not properly downloaded – perhaps blocked by a firewall), they will essentially get the default page that says "You can’t do this, you need JavaScript to set this stuff" and no facility to set it is displayed.

If JavaScript is available, though, it’s a slightly different scenario:

  • The warning message that’s set to display by default gets suppressed (after page load, by having the CSS display property set to none)
  • The form that accepts the user’s desired accesskey assignments is made visible (again, using JavaScript to change the CSS display property)

Now that we know that the user has JavaScript enabled and was able to get all the source files correctly, they can then go ahead and save their accesskeys.

Saving the Accesskey Choices

The process of saving the choices made by the user are fairly standard JavaScript/cookie methods – one long string is saved that has a number of property:value pairings (one for each link or button that can potentially be assigned an acccesskey). For example:

<form action="set-access-keys.htm" method="post" name="frmAccessKeys">
<fieldset>

<legend>Set Accesskeys for … </legend>

<div>
<label for="txthomelink">Home link</label>
<input value="" name="txthomelink" id="txthomelink" type="text" size="2" maxlength="1" />
</div>

<div>
<label for="txtsearchlink">Search Box</label>
<input value="" name="txtsearchlink" id="txtsearchlink" type="text" size="2" maxlength="1" />
</div>

...

</fieldset>
</form>

strKeyVals += "homelink=" + document.forms['frmAccessKeys'].txthomelink.value + "@@";
strKeyVals += "searchlink=" + document.forms[’frmAccessKeys’].txtsearchlink.value + "@@";

The @@ is simply there to make it easier to identify where to split the string when retrieving the data from the saved cookie later.

We also provide an option for the user to get a hint of the accesskey for each link or button with the use of a simple checkbox:

<div>
<input type="checkbox" name="chkshowhint" id="chkshowhint" />
<label for="chkshowhint" class="check">Show accesskeys in links and buttons (displayed after link in brackets)</label>
</div>

strKeyVals += "showhint=" + document.forms['frmAccessKeys'].chkshowhint.checked + "@@";

The process for capturing the information is initiated by an onclick event attached to the submit button, which passes it through to an addevent function:

addEvent(document.getElementById('cmdSubmit'), 'click', accessKeySetter, false);

(The function accessKeySetter is where the JavaScript code snippets above appear)

Remembering the Choices and Updating the Display on Page

Assuming the user has entered some values and also chosen to have the accesskeys showing in the links or buttons, once the page has reloaded following the submit, this is where the DOM really takes over.

Remember, we start with a page that has a number of links that have neither default accesskey or title attributes present, then we use JavaScript to present the option to manipulate/store accesskeys (if JavaScript is not available for whatever reason, the default page display is to warn them that they can’t do this and need to get their JS in order, or words to that effect). So, already, we’re using JavaScript in a nice progressive enhancing kinda way. The final stage is to use JavaScript and the DOM to update the page display depending on what’s found in the cookie. The steps to do this are as follows:

  1. Firstly, look for the presence of the accesskey cookie
  2. Assuming it’s found, take the content of that string and split it up into the individual links/buttons that it repesents (separated by the @@ characters that were shown earlier
    //get the cookie
    var strAccessKeys = readCookie("accesskeys");
    //split up the values
    var arrAccesskeys = strAccessKeys.split("@@");
  3. Then, each value found is split between the id of the link or button it represents and the value stored against each (the accesskey the user wants to assign)
    for (i=0;i<(arrAccesskeys.length-2);i++)
    {
    var thisAccessKey = arrAccesskeys[i].split("=");
    var accessKayEl = thisAccessKey[0];
    var accessKayAttrib = thisAccessKey[1];
  4. Then it’s a case of running through the array of ids in the cookie that have values assigned and matching them up to the ids of elements on the page and then:
    1. adding the accesskey value to the element
      if (accessKayAttrib.length>0)
      {
      //set the attribute
      document.getElementById(accessKayEl).setAttribute(’accesskey’, accessKayAttrib);
    2. and updating the content of link or value of the button text to display the accesskey (if the user has requested this), and adding a title attribute too:
      if (document.getElementById(accessKayEl).tagName=="A")
      {
      var strLinkPhrase = document.getElementById(accessKayEl).childNodes[0].nodeValue;
      }
      if ((document.getElementById(accessKayEl).tagName=="INPUT") && ((document.getElementById(accessKayEl).type=="submit")||
      (document.getElementById(accessKayEl).type=="button")))
      {
      var strLinkPhrase = document.getElementById(accessKayEl).value;
      }
      document.getElementById(accessKayEl).setAttribute(’title’, strLinkPhrase + ‘, access key = ‘ + accessKayAttrib);
      //display the accesskey hint, if that’s been chosen
      if (blnShowHints)
      {
      strLinkPhrase = strLinkPhrase + " [" + accessKayAttrib + "]";
      if (document.getElementById(accessKayEl).tagName=="A")
      {
      document.getElementById(accessKayEl).childNodes[0].nodeValue = strLinkPhrase;
      }
      if (document.getElementById(accessKayEl).tagName=="INPUT")
      {
      document.getElementById(accessKayEl).value = strLinkPhrase;
      }
  5. One final step, while traversing through the page, is to add the value chosen in to the form fields too, so they’re remembered, likewise the state of the checkbox.

The final result is a completely unobtrusive method for setting and remembering accesskey attributes which doesn’t enforce itself on the user – it’s completely opt-in.

[download the zipped version with all files required here.]

Possible Improvements

The biggest improvement that could happen with this script is to have it centralized somewhere, or some variant thereof, so that it could be re-used in some way. My thinking was that if this mechanism were possible to re-use across sites, then a user could potentially save some accesskey choices that would be usable for a number of different sites, but then there’s one fatal flaw in this idea – a cookie can only be read back by the same domain, thus if I were to set my choice for site whateverxyz.com, when I try to do the same for abcwhatever.com, it won’t be able to read back the same data. So I’m definitely open to suggestions as to how that might be made possible at some time in the future.

Outside Reading

This isn’t the first time someone has come up with a method for dealing with this, so here are a few other takes on how to solve the issue:

And if you want to to learn more about the background to the problem, here are a couple of recommended reads:

Continue reading 1

2 July 2006

I will admit straight up front – I don’t like writing long articles. There, I said it. I passed College English convincing my professors that I should be rewarded more for my creativity rather than the length of my prose. I’m the kind of guy that spent more time writing short stories than huge complicated essays. Eventually I went on to write for the school Newspaper, a.k.a. The State Hornet which gave me the freedom to keep my stories to a minimum of 500 words, or just a couple of paragraphs.

Today that legacy still stands and has transferred into my love for making widgets. No, not those widgets. I’m talking about those little goofy things you do on a Sunday afternoon from when you had that idea on Saturday night which left you frustrated on Friday after work. Yes, you’re not the only one with crazy ideas.

I’m talking about sliders, yoyos, tooltips, shakers, better looking code, faster queries, and random jubilees of DOM hoopla. It’s those small things that some of us live to create and find it rewarding even if it’s one line of code. And although these little works of ‘code art’ don’t always work on all browsers while having labels of ‘experimental’ written all over them, don’t feel ashamed of them. They come from you and you should embrace your work. You are, a Web Professional.

Segue

In this article I’m going to share some of my short stories, poems, and random sonnets of affectionate escapades I’ve had with JavaScript during the last few months, which I’ve built using the Yahoo UI utilities. Some are rather embarrassing, others useful, and yet others I feel are just downright sexy. If you see something you like, feel free to take it, tweak it, and make it your own. This is what some poetry afficionados of secret societies would call a poetry slam. So for want of a better phrase, this can be a <code slam>.

Why YUI?

The YUI utilities give me the expressive freedom to do what I want. And although comparing libraries is generally silly, the best illustration I can give is the Script.aculo.us Effects library vs the YUI Animation utility. Script.aculo.us offers you some pretty sweet looking effects like Fade, SlideUp, BlindDown, Shake, etc.. You can even add Parallel effects (way to go Thomas). On the flip side, the animation util doesn’t aim to give you these canned effects to package up with your web app, but rather it lets you make up your own effects by giving you the finely crafted tools (like a utility knife) to make what you want.

Another reason I like YUI is that it isn’t out to change the language itself, but rather it solves many of the cross-browser incompatibilities that we run into on a daily basis. Beyond that, it does quite a few other sexy things that I think you might like.

Code

Below is a compilation of fifteen things that I have either developed over the last few months, or have been inspired by a friend (who will receive full credit on the original idea). Enjoy!

DOM + The getElementBy’s

These days, when you’re working with the DOM, it’s all about getting the elements you want, with conditions applied. Thankfully the Dom util doesn’t go all out and try to do all the guesswork for you resulting in a bulky library with large sets of unwanted code. Instead it gives you a handy method for you to work with called getElementsBy.

YAHOO.util.Dom.getElementsByClassName

Let’s kick off our slam with the classic getElementsByClassName (which is actually in the utility by default). I like to think of it as our token example by the YUI folks showing off how getElementsBy can work natively within your functions. See Demo

getElementsByAttribute

This method is not in the Dom util by default, but demonstrates exactly how we can add it in if we wanted. See Demo

getElementsByExternal & getElementsByInternal

This function allows you to get outbound or inbound links returned to you as an HTMLElement collection. At that point it’s up to you what you want to do with your array of elements (eg. bind event listeners or run a method against them). Currently this is untested on IP address URIs, but I presume that won’t be an issue for most situations. See Demo

Random Effects of Love

Because everyone enjoys California cheese.

Pagination Slide Pattern

Paginating through large data sets and keeping someone’s attention can be a tedious task. Sliding results within a container is one way to keep a person entertained. See Demo

BlindUp & BlindDown (Yoyo)

These are classic effects that almost any animation lover would want in their toolkit. For the sake of making them a macro, here they are. See Demo

BlindOut

A demonstration of animating a box from any given corner. See Demo

Faux Mutation Events with CustomEvent

Most folk don’t know what mutation events are, mainly because they don’t work in IE6. These are events just like regular DOM events like ‘click’,’mouseover’, or ‘mouseout’ that you can listen for. Just to name a few there are ‘DOMNodeInserted’, ‘DOMNodeRemoved’, and ‘DOMNodeInsertedIntoDocument’. For a better reference you can see Wikipedia’s entry on DOM events and search for the Mutation events within the event table. Anyway, here’s how you can simulate some of those events using the CustomEvent class.

DOMNodeInserted

Create some elements, listen for the event, then make them draggable. See Demo

DOMNodeRemoved

Create some elements, then remove them to update an information box. See Demo

More with CustomEvents

There really is an endless amount of Custom Events that you can create. So we’ll keep this to a minimum. Here are three places that the CustomEvent can come in useful:

Extending the Drag and Drop

Currently, the Drag and Drop utility is packaged with quite a few custom events you can override such as startDrag, onDrag, or endDrag. However the problem is that you can’t subscribe to any of these, only override. Here is one trick you can use to make them subscribable. See Demo

onMenuOpen & onMenuCollapse

This is a combination of animation and custom events where we show menu items sliding into view and firing off subscribable events. See Demo

General Recipes

None of these have any rhyme or reason. But I tend to think that in some place or another, they can solve a problem.

Sweet Titles (YUI Style)

Here we have an OO version of Sweet Titles that allows us to declare multiple instances of tooltips on separate elements with some optional configuration parameters you can pass into the constructor object to get that exact look and feel you want out of a tooltip. See Demo

Faux Lightbox Effect

For the sake of doing something that’s already been done, here’s a faux light box effect that can be obtained using some core tools. See Demo

Photo Batching

YAHOO.util.Dom.batch alone may convince you of how cool the DOM Collection utility can be. This shows you how you can batch an HTML Element collection to a method. (Thanks Matt Sweeny) See Demo

Unobtrusive new windows links

For some reason or another, this always seems to create some noise regardless of its controversial nature. Here is a very simple way to do it with Event, DOM, and some of the tools we’ve defined in this very article. See Demo

Layout Switching with Grids

This is a combination of using the CSS Grids toolkit and swapping out the templates using JavaScript animation See Demo

That’s All Folks

At least for this article’s sake. There are plenty of other things you can making using the Yahoo! UI utilities and this has only scratched the surface. I hope it was as fun for you as it was for me.

digg.com logo Like this article? Digg it!

Continue reading 33

Sign Up to our Newsletter

Enter your e-mail address below to receive regular updates on web design, web development and web business. Subscribe today and receive a free 44 page PDF "Designing Web User Interfaces" by Ryan Singer of 37signals.

Subscribe to the Think Vitamin articles RSS feed

CSS3 Online Conference March 22nd 2010

News

Twitter

Follow us on Twitter

Subscribe

Article Subscribers

Feedburner blog subscriber indicator

News Subscribers

Feedburner blog subscriber indicator

Subscribe by Email

You can receive Think Vitamin updates via email. Just pop your email address in the box below and click the arrows.

Subscribe by RSS

You can also receive new Think Vitamin posts via your RSS feed reader

Subscribe RSS Think Vitamin is a proud member of the Smashing Network

Ads Via The Deck