the dom is a mess @ yahoo

79
The DOM is a Mess John Resig http://ejohn.org / - http://twitter.com/jeresig/

Upload: jeresig

Post on 08-Sep-2014

51.094 views

Category:

Technology


0 download

DESCRIPTION

A talk that I gave at Yahoo, in January 2009, about the DOM.

TRANSCRIPT

Page 1: The DOM is a Mess @ Yahoo

The DOM is a MessJohn Resig

http://ejohn.org/ - http://twitter.com/jeresig/

Page 2: The DOM is a Mess @ Yahoo

A Tour of the DOM✦ A messy DOM✦ Writing Cross-Browser Code✦ Common Features

✦ CSS Selector Engine✦ DOM Modification✦ Events

Page 3: The DOM is a Mess @ Yahoo

Messy✦ Nearly every DOM method is broken in

some way, in some browser.✦ Some old:

✦ getElementById✦ getElementsByTagName

✦ Some new:✦ getElementsByClassName✦ querySelectorAll

Page 4: The DOM is a Mess @ Yahoo

getElementById✦ Likely the most commonly used DOM

method✦ A couple weird bits:

✦ IE and older versions of Opera returning elements with a name == id

✦ Does not easily work in XML documents

Page 5: The DOM is a Mess @ Yahoo

getElementsByTagName✦ Likely tied for most-commonly-used

DOM method✦ Riddled with bugs in IE:

✦ “*” returns no elements in IE 5.5✦ “*” returns no elements on <object>

elements in IE 7✦ .length gets overwritten in IE if an

element with an ID=”length” is found

Page 6: The DOM is a Mess @ Yahoo

getElementsByClassName✦ Landed in Firefox 3, Safari 3, Opera 9.6✦ A few knotty issues:

✦ HTMLElement.prototype .getElementsByClassNamecouldn’t be overwritten in Firefox

✦ Opera doesn’t match a second-specified class (e.g. class=”a b”, b isn’t found)

Page 7: The DOM is a Mess @ Yahoo

querySelectorAll✦ Find DOM elements using CSS selectors✦ In Firefox 3.1, Safari 3.1, Opera 10, IE 8✦ Birthing pains:

✦ Doesn’t exist in quirks mode, in IE 8✦ Safari 3.1 had memory out of bounds

problems✦ Safari 3.2 can’t match uppercase

characters in quirks mode✦ #id doesn’t match in XML documents

Page 8: The DOM is a Mess @ Yahoo

Moral✦ If there’s a DOM method, there’s probably

a problem with it somewhere, in some capacity.

Page 9: The DOM is a Mess @ Yahoo

Cross-Browser Code

Page 10: The DOM is a Mess @ Yahoo

Strategies✦ Pick your browsers✦ Know your enemies✦ Write your code

Page 11: The DOM is a Mess @ Yahoo

Cost / Benefit

IE 7 IE 6 FF 3 Safari 3 Opera 9.5

Cost Benefit

Draw a line in the sand.

Page 12: The DOM is a Mess @ Yahoo

Graded Support

Yahoo Browser Compatibility

Page 13: The DOM is a Mess @ Yahoo

Browser Support GridIE Firefox Safari Opera Chrome

Previous 6.0 2.0 3.0 9.5

Current 7.0 3.0 3.2 9.6 Current

Next 8.0 3.1 4.0 10.0

jQuery Browser Support

Page 14: The DOM is a Mess @ Yahoo

Browser Support GridIE Firefox Safari Opera Chrome

Previous 6.0 2.0 3.0 9.5

Current 7.0 3.0 3.2 9.6 Current

Next 8.0 3.1 4.0 10.0

jQuery 1.3 Browser Support

Page 15: The DOM is a Mess @ Yahoo

Know Your Enemies

JavaScript CodeMissing Features

Regressions

Browser Bugs

ExternalCode, Markup

Bug Fixes

Points of Concern for JavaScript Code

Page 16: The DOM is a Mess @ Yahoo

Know Your Enemies

JavaScript CodeMissing Features

Regressions

Browser Bugs

ExternalCode, Markup

Bug Fixes

Points of Concern for JavaScript Code

Page 17: The DOM is a Mess @ Yahoo

Browser Bugs✦ Generally your primary concern✦ Your defense is a good test suite

✦ Prevent library regressions✦ Analyze upcoming browser releases

✦ Your offense is feature simulation✦ What is a bug?

✦ Is unspecified, undocumented, behavior capable of being buggy?

Page 18: The DOM is a Mess @ Yahoo

Test, Test, Test

1446 Tests, 9 browsers

Page 19: The DOM is a Mess @ Yahoo

Know Your Enemies

JavaScript CodeMissing Features

Regressions

Browser Bugs

ExternalCode, Markup

Bug Fixes

Points of Concern for JavaScript Code

Page 20: The DOM is a Mess @ Yahoo

External Code✦ Making your code resistant to any

environment✦ Found through trial and error✦ Integrate into your test suite

✦ Other libraries✦ Strange code uses

Page 21: The DOM is a Mess @ Yahoo

Environment Testing✦ 100% Passing:

✦ Standards Mode✦ Quirks Mode✦ Inline with Prototype + Scriptaculous✦ Inline with MooTools

✦ Work in Progress:✦ XHTML w/ correct mimetype✦ With Object.prototype✦ In XUL (Firefox Extensions)✦ In Rhino (with Env.js)

Page 22: The DOM is a Mess @ Yahoo

Object.prototype Object.prototype.otherKey = "otherValue";    var obj = { key: "value" };  for ( var prop in object ) {    if ( object.hasOwnProperty( prop ) ) {      assert( prop, "key",  "There should only be one iterated property." );    }  } 

Page 23: The DOM is a Mess @ Yahoo

Greedy IDs <form id="form">    <input type="text" id="length"/>    <input type="submit" id="submit"/>  </form> 

document.getElementsByTagName("input").length

Page 24: The DOM is a Mess @ Yahoo

Order of Stylesheets✦ Putting stylesheets before code guarantees

that they’ll load before the code runs.✦ Putting them after can create an

indeterminate situation.

Page 25: The DOM is a Mess @ Yahoo

Pollution✦ Make sure your code doesn’t break

outside code✦ Use strict code namespacing✦ Don’t extend outside objects, elements

✦ Bad:✦ Introducing global variables✦ Extending native objects (Array, Object)✦ Extending DOM natives

Page 27: The DOM is a Mess @ Yahoo

Know Your Enemies

JavaScript CodeMissing Features

Regressions

Browser Bugs

ExternalCode, Markup

Bug Fixes

Points of Concern for JavaScript Code

Page 28: The DOM is a Mess @ Yahoo

Missing Features✦ Typically older browsers missing specific

features✦ Optimal solution is to gracefully

degrade✦ Fall back to a simplified page

✦ Can’t make assumptions about browsers that you can’t support✦ If it’s impossible to test them, you must

provide a graceful fallback✦ Object detection works well here.

Page 29: The DOM is a Mess @ Yahoo

Object Detection✦ Check to see if an object or property

exists✦ Useful for detecting an APIs existence✦ Doesn’t test the compatibility of an API

✦ Bugs can still exist - need to test those separately with feature simulation

Page 30: The DOM is a Mess @ Yahoo

Event Binding function attachEvent( elem, type, handle ) {    // bind event using proper DOM means    if ( elem.addEventListener )      elem.addEventListener(type, handle, false);          // use the Internet Explorer API    else if ( elem.attachEvent )      elem.attachEvent("on" + type, handle);  } 

Page 31: The DOM is a Mess @ Yahoo

Fallback Detection if ( typeof document !== "undefined" &&        (document.addEventListener  || document.attachEvent) &&       document.getElementsByTagName &&  document.getElementById ) {    // We have enough of an API to  // work with to build our application  } else {    // Provide Fallback  }

Page 32: The DOM is a Mess @ Yahoo

Fallback✦ Figure out a way to reduce the

experience✦ Opt to not execute any JavaScript

✦ Guarantee no partial API✦ (e.g. DOM traversal, but no Events)

✦ Redirect to another page, or just work unobtrusively

✦ Working on a ready() fallback for jQuery

Page 33: The DOM is a Mess @ Yahoo

Know Your Enemies

JavaScript CodeMissing Features

Regressions

Browser Bugs

ExternalCode, Markup

Bug Fixes

Points of Concern for JavaScript Code

Page 34: The DOM is a Mess @ Yahoo

Bug Fixes✦ Don’t make assumptions about browser

bugs.✦ Assuming that a browser will always

have a bug is foolhardy✦ You will become susceptible to fixes✦ Browsers will become less inclined to fix

bugs✦ Look to standards to make decisions about

what are bugs

Page 35: The DOM is a Mess @ Yahoo

Failed Bug Fix in FF 3 // Shouldn't work  var node = documentA.createElement("div");  documentB.documentElement.appendChild( node );    // Proper way  var node = documentA.createElement("div");  documentB.adoptNode( node );  documentB.documentElement.appendChild( node ); 

Page 36: The DOM is a Mess @ Yahoo

Feature Simulation✦ More advanced than object detection✦ Make sure an API works as advertised✦ Able to capture bug fixes gracefully

Page 37: The DOM is a Mess @ Yahoo

Verify API // Run once, at the beginning of the program  var ELEMENTS_ONLY = (function(){    var div = document.createElement("div");    div.appendChild( document.createComment("test" ) );    return div.getElementsByTagName("*").length === 0;  })();    // Later on:  var all = document.getElementsByTagName("*");    if ( ELEMENTS_ONLY ) {    for ( var i = 0; i < all.length; i++ ) {      action( all[i] );    }  } else {    for ( var i = 0; i < all.length; i++ ) {      if ( all[i].nodeType === 1 ) {        action( all[i] );      }    }  } 

Page 38: The DOM is a Mess @ Yahoo

Figure Out Naming <div id="test" style="color:red;"></div>  <div id="test2"></div>  <script>  // Perform the initial attribute check  var STYLE_NAME = (function(){    var div = document.createElement("div");    div.style.color = "red";        if ( div.getAttribute("style") )      return "style";        if ( div.getAttribute("cssText") )      return "cssText";  })();    // Later on:  window.onload = function(){    document.getElementsById("test2").setAttribute( STYLE_NAME,        document.getElementById("test").getAttribute( STYLE_NAME ) );  };  </script> 

Page 39: The DOM is a Mess @ Yahoo

Know Your Enemies

JavaScript CodeMissing Features

Regressions

Browser Bugs

ExternalCode, Markup

Bug Fixes

Points of Concern for JavaScript Code

Page 40: The DOM is a Mess @ Yahoo

Regressions✦ Removing or changing unspecified APIs✦ Object detection helps here✦ Monitor upcoming browser releases

✦ All vendors provide access to beta releases

✦ Diligence!✦ Example: IE 7 introduced

XMLHttpRequest with file:// bug✦ Test Suite Integration

Page 41: The DOM is a Mess @ Yahoo

Object Failover function attachEvent( elem, type, handle ) {    // bind event using proper DOM means    if ( elem.addEventListener )      elem.addEventListener(type, handle, false);          // use the Internet Explorer API    else if ( elem.attachEvent )      elem.attachEvent("on" + type, handle);  } 

Page 42: The DOM is a Mess @ Yahoo

Safe Cross-Browser Fixes✦ The easiest form of fix✦ Unifies an API across browsers✦ Implementation is painless

Page 43: The DOM is a Mess @ Yahoo

Unify Dimensions // ignore negative width and height values  if ( (key == 'width' || key == 'height') &&  parseFloat(value) < 0 )    value = undefined; 

Page 44: The DOM is a Mess @ Yahoo

Prevent Breakage if ( name == "type" && elem.nodeName.toLowerCase()  == "input" && elem.parentNode )    throw "type attribute can't be changed"; 

Page 45: The DOM is a Mess @ Yahoo

Untestable Problems✦ Has an event handler been bound?✦ Will an event fire?✦ Do CSS properties like color or opacity

actually affect the display?✦ Problems that cause a browser crash.✦ Problems that cause an incongruous API.

Page 46: The DOM is a Mess @ Yahoo

Impractical to Test✦ Performance-related issues✦ Determining if Ajax requests will work

Page 47: The DOM is a Mess @ Yahoo

Battle of Assumptions✦ Cross-browser development is all about

reducing the number of assumptions✦ No assumptions indicates perfect code

✦ Unfortunately that’s an unobtainable goal

✦ Prohibitively expensive to write✦ Have to draw a line at some point

Page 48: The DOM is a Mess @ Yahoo

DOM Traversal✦ Many methods of DOM traversal✦ One unanimous solution:

✦ CSS Selector Engine

Page 49: The DOM is a Mess @ Yahoo

Traditional DOM✦ getElementsByTagName✦ getElementById✦ getElementsByClassName

✦ in FF3, Safari 3, Opera 9.6✦ .children

✦ only returns elements (in all, and FF 3.1)✦ getElementsByName✦ .all[id]

✦ Match multiple elements by ID

Page 50: The DOM is a Mess @ Yahoo

Top-Down CSS Selector✦ Traditional style of traversal

✦ Used by all major libraries✦ Work from left-to-right✦ “div p”

✦ Find all divs, find paragraphs inside✦ Requires a lot of result merging✦ And removal of duplicates

Page 51: The DOM is a Mess @ Yahoo

   function find(selector, root){      root = root || document; 

           var parts = selector.split(" "),        query = parts[0],        rest = parts.slice(1).join(" "),        elems = root.getElementsByTagName( query ),        results = []; 

           for ( var i = 0; i < elems.length; i++ ) {        if ( rest ) {          results = results.concat( find(rest, elems[i]) );        } else {          results.push( elems[i] );        }      } 

           return results;    } 

Page 52: The DOM is a Mess @ Yahoo

 (function(){    var run = 0; 

       this.unique = function( array ) {      var ret = []; 

           run++; 

       for ( var i = 0, length = array.length; i < length; i++ ) {        var elem = array[ i ]; 

         if ( elem.uniqueID !== run ) {          elem.uniqueID = run;          ret.push( array[ i ] );        }      } 

       return ret;    };  })(); 

Page 53: The DOM is a Mess @ Yahoo

Bottom-Up✦ Work from right-to-left

✦ (How CSS Engines work in browsers.)✦ “div p”

✦ Find all paragraphs, see if they have a div ancestor, etc.

✦ Fast for specific queries✦ “div #foo”

✦ Deep queries get slow (in comparison)✦ “#foo p”

Page 54: The DOM is a Mess @ Yahoo

Bottom-Up✦ Some nice features:

✦ Only one DOM query✦ No merge/unique required (except for “div, span”)

✦ Everything is a process of filtering

Page 55: The DOM is a Mess @ Yahoo

   function find(selector, root){      root = root || document; 

           var parts = selector.split(" "),        query = parts[parts.length - 1],        rest = parts.slice(0,-1).join("").toUpperCase(),        elems = root.getElementsByTagName( query ),        results = []; 

           for ( var i = 0; i < elems.length; i++ ) {        if ( rest ) {          var parent = elems[i].parentNode;          while ( parent && parent.nodeName != rest ) {            parent = parent.parentNode;          } 

                   if ( parent ) {            results.push( elems[i] );          }        } else {          results.push( elems[i] );        }      } 

           return results;    } 

Page 56: The DOM is a Mess @ Yahoo

CSS to XPath✦ Browsers provide XPath functionality✦ Collect elements from a document✦ Works in all browsers

✦ In IE it only works on HTML documents

✦ Fast for a number of selectors (.class, “div div div”)✦ Slow for some others: #id

✦ Currently used by Dojo and Prototype

Page 57: The DOM is a Mess @ Yahoo

 if ( typeof document.evaluate === "function" ) {    function getElementsByXPath(expression, parentElement) {      var results = [];      var query = document.evaluate(expression, parentElement || document,        null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);      for (var i = 0, length = query.snapshotLength; i < length; i++)        results.push(query.snapshotItem(i));      return results;    }  } 

Page 58: The DOM is a Mess @ Yahoo

Goal CSS 3 XPath

All Elements * //*

All P Elements p //p

All Child Elements p > * //p/*

Element By ID #foo //*[@id='foo']

Element By Class .foo //*[contains(concat(" ", @class, " ")," foo ")]

Element With Attribute *[title] //*[@title]

First Child of All P p > *:first-child //p/*[0]

All P with an A descendant Not possible //p[a]

Next Element p + * //p/following-sibling::*[0]

Page 59: The DOM is a Mess @ Yahoo

querySelectorAll✦ The Selectors API spec from the W3C✦ Two methods:

✦ querySelector (first element)✦ querySelectorAll (all elements)

✦ Works on:✦ document✦ elements✦ DocumentFragments

✦ Implemented in:✦ Firefox 3.1, Safari 3, Opera 10, IE 8

Page 60: The DOM is a Mess @ Yahoo

 <div id="test">    <b>Hello</b>, I'm a ninja!  </div>  <div id="test2"></div>  <script>  window.onload = function(){    var divs = document.querySelectorAll("body > div");    assert( divs.length === 2, "Two divs found using a CSS selector." ); 

       var b = document.getElementById("test").querySelector("b:only-child");    assert( b, "The bold element was found relative to another element." ); };  </script> 

Page 61: The DOM is a Mess @ Yahoo

 <div id="test">    <b>Hello</b>, I'm a ninja!  </div>  <script>  window.onload = function(){    var b = document.getElementById("test").querySelector("div b");    assert( b, "Only the last part of the selector matters." ); };  </script> 

Page 62: The DOM is a Mess @ Yahoo

DOM Modification✦ Injecting HTML✦ Removing Elements

Page 63: The DOM is a Mess @ Yahoo

Injecting HTML✦ HTML 5:

insertAdjacentHTML✦ Already in IE, dicey support, at best✦ What can we use instead?

✦ We must generate our own HTML injection

✦ Use innerHTML to generate a DOM

Page 64: The DOM is a Mess @ Yahoo

 function getNodes(htmlString){    var map = {      "<td": [3, "<table><tbody><tr>", "</tr></tbody></table>"],      "<option": [1, "<select multiple='multiple'>", "</select>"]      // a full list of all element fixes    }; 

       var name = htmlString.match(/<\w+/),      node = name ? map[ name[0] ] || [0, "", ""]; 

       var div = document.createElement("div");    div.innerHTML = node[1] + htmlString + node[2]; 

       while ( node[0]-- )      div = div.lastChild; 

       return div.childNodes;  } 

   assert( getNodes("<td>test</td><td>test2</td>").length === 2,    "Get two nodes back from the method." );  assert( getNodes("<td>test</td>").nodeName === "TD",    "Verify that we're getting the right node." ); 

Page 65: The DOM is a Mess @ Yahoo

Element Mappings✦ option and optgroup need to be contained in a

<select multiple="multiple">...</select>✦ legend need to be contained in a

<fieldset>...</fieldset>✦ thead, tbody, tfoot, colgroup, and caption need to be

contained in a <table>...</table>✦ tr need to be in a

<table><thead>...</thead></table>, <table><tbody>...</tbody></table>, or a <table><tfoot>...</tfoot></table>

✦ td and th need to be in a<table><tbody><tr>...</tr></tbody></table>

✦ col in a<table><tbody></tbody><colgroup>...</colgroup></table>

✦ link and script need to be in adiv<div>...</div>

Page 66: The DOM is a Mess @ Yahoo

DocumentFragment✦ Fragments can collect nodes✦ Can be appended or cloned in bulk✦ Super-fast (2-3x faster than normal)

Page 67: The DOM is a Mess @ Yahoo

   function insert(elems, args, callback){      if ( elems.length ) {        var doc = elems[0].ownerDocument || elems[0],          fragment = doc.createDocumentFragment(),          scripts = getNodes( args, doc, fragment ),          first = fragment.firstChild; 

             if ( first ) {          for ( var i = 0; elems[i]; i++ ) {            callback.call( root(elems[i], first),               i > 0 ? fragment.cloneNode(true) : fragment );          }        }      }    } 

       var divs = document.getElementsByTagName("div"); 

       insert(divs, ["Name:"], function(fragment){      this.appendChild( fragment );    }); 

       insert(divs, ["First Last"], function(fragment){      this.parentNode.insertBefore( fragment, this );    }); 

Page 68: The DOM is a Mess @ Yahoo

Inline Script Execution✦ .append(“<script>var foo = 5;</script>”);✦ Must execute scripts globally✦ window.execScript() (for IE)✦ eval.call( window, “var foo = 5;” );✦ Cross-browser way is to build a script

element then inject it✦ Executes globally

Page 69: The DOM is a Mess @ Yahoo

 function globalEval( data ) {    data = data.replace(/^\s+|\s+$/g, ""); 

       if ( data ) {      var head = document.getElementsByTagName("head")[0] || document.documentElement,        script = document.createElement("script"); 

           script.type = "text/javascript";      script.text = data; 

           head.insertBefore( script, head.firstChild );      head.removeChild( script );    }  } 

Page 70: The DOM is a Mess @ Yahoo

Removing Elements✦ Have to clean up bound events

✦ IE memory leaks✦ Easy to do if it’s managed.

Page 71: The DOM is a Mess @ Yahoo

Events✦ Three big problems with Events:

✦ Memory Leaks (in IE)✦ Maintaining ‘this’ (in IE)✦ Fixing event object inconsistencies

Page 72: The DOM is a Mess @ Yahoo

Leaks✦ Internet Explorer 6 leaks horribly

✦ Other IEs still leak, not so badly✦ Attaching functions (that have a closure to

another node) as properties✦ Makes a leak:

elem.test = function(){ anotherElem.className = “foo”;};

Page 73: The DOM is a Mess @ Yahoo

‘this’✦ Users like having their ‘this’ refer to the

target element✦ Not the case in IE✦ What’s the solution?

Page 74: The DOM is a Mess @ Yahoo

Single Handler✦ Bind a single handler to an event✦ Call each bound function individually✦ Can fix ‘this’ and event object✦ How do we store the bound functions in a

way that won’t leak?

Page 75: The DOM is a Mess @ Yahoo

Central Data Store✦ Store all bound event handlers in a central

object✦ Link elements to handlers✦ Keep good separation✦ One data store per library instance✦ Easy to manipulate later

✦ Trigger individual handlers✦ Easy to remove again, later

Page 76: The DOM is a Mess @ Yahoo

Central Data StoreElement

Element

Element

Element

Element

#43

#67

#22

Data

Data

Events

Events

function click(){}function mouseover(){}

function click(){}

function click(){}

Data Store

Page 77: The DOM is a Mess @ Yahoo

Multiple StoresElement

Element

Element

Element

Element

#43

#67

#22

Library A

Library B

#37

Page 78: The DOM is a Mess @ Yahoo

Unique Element ID✦ The structure must hook to an element✦ Elements don’t have unique IDs✦ Must generate them and manage them✦ jQuery.data( elem, “events” );✦ Unique attribute name:

✦ elem.jQuery123456789 = 45;✦ Prevents collisions with other libraries

✦ We can store all sorts of data in here

Page 79: The DOM is a Mess @ Yahoo

Questions?✦ http://ejohn.org/✦ http://twitter.com/jeresig/