Showing posts with label DOM. Show all posts
Showing posts with label DOM. Show all posts

Custom data in HTML5 tags

Quite often I’d like to add application data to my HTML markup to pass information between server-side scripts generating the HTML markup and client-side jQuery scripts. Prior to HTML5 you could decide to use XHTML and your private namespace; numerous applications (including Facebook and my web sites) use this approach. It works nicely unless you’ve decided to use client-side XSLT transformation in Firefox.

HTML5 gives you another option: embedding custom non-visible data in HTML tags. You can add as many attributes as you wish to a HTML tag as long as they start with the data- prefix. HTML5 compliant browsers will eventually give you DOM access to these attributes through the dataset attribute; in most browsers (ancient IE or Netscape releases might not work) you can get these attributes with the getAttribute (or jQuery attr) call.

You might wonder what the difference is between using non-standard attributes of your choice and standard data- attributes as long as the browsers don’t support the dataset property. If nothing else, your HTML code will be validated by HTML5 validator.

Keyboard shortcuts in web user interface

Keyboard shortcuts can make an application significantly quicker to use; however, if you want to implement then in your web applications, you'll have to wade through murky waters of non-standard keyboard events (the only major browser event type not standardized in currently supported DOM standards). I've documented most of the pitfalls (and the workarounds) in my InformIT article Adding Keyboard Shortcuts to Your Web User Interface.

Emulating activeElement property with DOM browsers

Sometimes you need to know which page element has the focus. Internet Explorer solves the problem nicely with the document.activeElement property, which is unfortunately a proprietary extension and thus not available in other DOM-compliant browsers.

All the solutions I've found with Google were kludges along the lines of “scan all the elements and test each one to see whether it has focus”. It's much better to use the capture phase of the DOM event model and track the focus and blur events with the document object:
function _dom_trackActiveElement(evt) {
  if (evt && evt.target) {
    document.activeElement =
        evt.target == document ? null : evt.target;
  }
}
 
function _dom_trackActiveElementLost(evt) {
  document.activeElement = null; }
 
if (document.addEventListener) {
  document.addEventListener("focus",_dom_trackActiveElement,true);
  document.addEventListener("blur",_dom_trackActiveElementLost,true);
}

This implementation is slightly over-pessimistic; if the browser window loses focus, the activeElement is set to null (as the input control loses focus as well). If your application needs the activeElement value even when the browser window doesn't have the focus, you could remove the blur event listener.

Mouse event bubbling might produce unexpected results

The unexpected consequences of the mouse event bubbling are best illustrated with an example. Consider the following HTML markup (and note that in real life you should not declare event handlers in HTML markup):
<div onmouseover="d_a()" onmouseout="d_b()">
  <p onmouseover="p_a()" onmouseout="p_b()">Line 1</p>
  <p>Line 2</p>
</div>
When the mouse is moved from outside the DIV to the first line, d_a and p_a are called as expected. When the mouse moves from Line 1 to Line 2, p_b (expected), as well as p_a (highly unexpected) are called even though the mouse has not left the DIV. p_a is called due to event bubbling; the mouseover event bubbles from its target through all the target's ancestors toward the document object.

Probably the best workaround is to compare the event target (browser-dependent, use xEvent to get browser-independent code) with the current element.

On-demand load of JavaScript libraries

With a creative approach to DOM model, you can load JavaScript libraries on demand from your web pages. What you need to do is to create a SCRIPT element, set its SRC attribute and insert it into the document. You could use a function similar to this one ...
function LoadScript(url)
{
  var lib = document.createElement(’script’);
  lib.type=’text/javascript’;
  lib.src= url;
  document.getElementsByTagName(’head’)[0].appendChild(s);
}

... or you could use a safer xLoadScript function from the X library.

The X library also provides a xSmartLoadScript function to stop you from loading the same library multiple times.

In a recent post on PeachPit.com, Kris Hadlock gives you a few ideas where this functionality might come handy.

DOM events don't have the “on” prefix

One of the very common mistakes when switching from traditional object-property-based event registration (window.onload = handler) to DOM event registration (window.addEventListener('load',handler)) is the reuse of object property as the event name. The “on” property prefix should not be part of the event name (for example, use load instead of onload and mouseover instead of onmouseover).

To make matters worse, the attachEvent method (Microsoft equivalent of the addEventListener DOM method) expects the event names with the “on” prefix.

You have to use cells array, not DOM children to select table cells

Recently, I wanted to use DOM property firstChild to select the first cell in a TABLE row ... and got quite unexpected results, the firstChild in my case was a text node. Once I've warmed up my grey cells, it all became quite obvious. For example, in the following HTML markup ...
<table>
  <tr id='test'>
    <td>Cell #1</td>
    <td>Cell #2</td>
  </tr>
</table>
... the first child of the TR element is the whitespace between the TR tag and the first TD tag. As the whitespace is allowed in that position by the HTML standard, you cannot rely on the TR.firstChild property. You should use TR.cells[0] to select the first cell.

Firefox document object lacks DOM properties in XSLT-generated pages

I started working on a keyboard-shortcut library and knew (somewhere in the hidden inaccessible depths of my mind) that I had a problem with document.body property a while ago. As it's required by the HTML DOM standard, I immediately suspected that it was an IE problem ... and chased that ghost for a while, until I've realized how wrong I was.

The buggy browser was Firefox: if you create a web page with local XSLT transformation (dictated by the xml-stylesheet instruction in the XML document returned from the server) in Firefox (up to at least 2.0.0.6), everything looks normal, but document.body is null and document.forms, document.links and other similar array are empty.

To make matters more confusing, all other HTML DOM calls work (it's a different bug from this one), so I had to substitute the links array with document.getElementsByTagName("A") and the body property with the document.getElementsById("body") (and used the id attribute on the body tag).

Firefox: HTML DOM fails if a local XSL transformation does not specify xsl:output

This nasty beast took me a while to figure out: if you do local XSL transforms in Firefox 1.5 and don't have the xsl:output directive in your main XSL stylesheet, HTML-specific DOM calls might not work.

The results are weird: for example, whatever is produced with the XSL transformation behaves like a HTML DOM object, but if you create a new element, it lacks HTML-specific DOM calls.

And the obvious solution: make sure you're always including ...
<xsl:output method="html" encoding="utf-8"
doctype-public="-//W3C//DTD XHTML 1.0 Transitional//EN"
doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"/>
... in your XSL stylesheet.

IE7 bug: addEventListener does not work with table row mouseover event

If you want to implement your JavaScript code using DOM-compliant events, you'd be unpleasantly surprised to learn that table rows in Internet Explorer do not respond to mouseover events attached to them with addEventListener. Firefox and Opera work as expected. The only way to achieve cross-browser compatibility is to use the old onmouseover properties: tablerow.onmouseover = myfunc; instead of tablerow.addEventListener("mouseover",myfunc).