Advertisement

More Related Content

Slideshows for you(20)

Advertisement
Advertisement

The DOM is a Mess @ Yahoo

  1. The DOM is a Mess John Resig http://ejohn.org/ - http://twitter.com/jeresig/
  2. A Tour of the DOM A messy DOM ✦ Writing Cross-Browser Code ✦ Common Features ✦ ✦ CSS Selector Engine ✦ DOM Modification ✦ Events
  3. Messy Nearly every DOM method is broken in ✦ some way, in some browser. Some old: ✦ ✦ getElementById ✦ getElementsByTagName Some new: ✦ ✦ getElementsByClassName ✦ querySelectorAll
  4. 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
  5. 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
  6. getElementsByClassName Landed in Firefox 3, Safari 3, Opera 9.6 ✦ A few knotty issues: ✦ ✦ HTMLElement.prototype .getElementsByClassName couldn’t be overwritten in Firefox ✦ Opera doesn’t match a second-specified class (e.g. class=”a b”, b isn’t found)
  7. 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
  8. Moral If there’s a DOM method, there’s probably ✦ a problem with it somewhere, in some capacity.
  9. Cross-Browser Code
  10. Strategies Pick your browsers ✦ Know your enemies ✦ Write your code ✦
  11. Cost / Benefit IE 7 IE 6 FF 3 Safari 3 Opera 9.5 Cost Benefit Draw a line in the sand.
  12. Graded Support Yahoo Browser Compatibility
  13. Browser Support Grid IE 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
  14. Browser Support Grid IE Firefox Safari Opera Chrome Previous 3.0 9.5 6.0 2.0 Current 7.0 3.0 3.2 9.6 Current Next 3.1 4.0 8.0 10.0 jQuery 1.3 Browser Support
  15. Know Your Enemies Points of Concern for JavaScript Code Browser Bugs Regressions Missing Features JavaScript Code Bug Fixes External Code, Markup
  16. Know Your Enemies Points of Concern for JavaScript Code Browser Bugs Regressions Missing Features JavaScript Code Bug Fixes External Code, Markup
  17. 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?
  18. Test, Test, Test 1446 Tests, 9 browsers
  19. Know Your Enemies Points of Concern for JavaScript Code Browser Bugs Regressions Missing Features JavaScript Code Bug Fixes External Code, Markup
  20. External Code Making your code resistant to any ✦ environment ✦ Found through trial and error ✦ Integrate into your test suite ✦ Other libraries ✦ Strange code uses
  21. 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)
  22. Object.prototype  Object.prototype.otherKey = quot;otherValuequot;;      var obj = { key: quot;valuequot; };   for ( var prop in object ) {     if ( object.hasOwnProperty( prop ) ) {       assert( prop, quot;keyquot;,  quot;There should only be one iterated property.quot; );     }   } 
  23. Greedy IDs  <form id=quot;formquot;>     <input type=quot;textquot; id=quot;lengthquot;/>     <input type=quot;submitquot; id=quot;submitquot;/>   </form>  document.getElementsByTagName(quot;inputquot;).length
  24. Order of Stylesheets Putting stylesheets before code guarantees ✦ that they’ll load before the code runs. Putting them after can create an ✦ indeterminate situation.
  25. 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
  26. Pollution http://mankz.com/code/ GlobalCheck.htm
  27. Know Your Enemies Points of Concern for JavaScript Code Browser Bugs Regressions Missing Features JavaScript Code Bug Fixes External Code, Markup
  28. 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. ✦
  29. 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
  30. 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(quot;onquot; + type, handle);   } 
  31. Fallback Detection  if ( typeof document !== quot;undefinedquot; &&         (document.addEventListener  || document.attachEvent) &&        document.getElementsByTagName &&  document.getElementById ) {     // We have enough of an API to  // work with to build our application   } else {     // Provide Fallback   }
  32. 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 ✦
  33. Know Your Enemies Points of Concern for JavaScript Code Browser Bugs Regressions Missing Features JavaScript Code Bug Fixes External Code, Markup
  34. 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
  35. Failed Bug Fix in FF 3  // Shouldn't work   var node = documentA.createElement(quot;divquot;);   documentB.documentElement.appendChild( node );      // Proper way   var node = documentA.createElement(quot;divquot;);   documentB.adoptNode( node );   documentB.documentElement.appendChild( node ); 
  36. Feature Simulation More advanced than object detection ✦ Make sure an API works as advertised ✦ Able to capture bug fixes gracefully ✦
  37. Verify API  // Run once, at the beginning of the program   var ELEMENTS_ONLY = (function(){     var div = document.createElement(quot;divquot;);     div.appendChild( document.createComment(quot;testquot; ) );     return div.getElementsByTagName(quot;*quot;).length === 0;   })();      // Later on:   var all = document.getElementsByTagName(quot;*quot;);      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] );       }     }   } 
  38. Figure Out Naming  <div id=quot;testquot; style=quot;color:red;quot;></div>   <div id=quot;test2quot;></div>   <script>   // Perform the initial attribute check   var STYLE_NAME = (function(){     var div = document.createElement(quot;divquot;);     div.style.color = quot;redquot;;          if ( div.getAttribute(quot;stylequot;) )       return quot;stylequot;;          if ( div.getAttribute(quot;cssTextquot;) )       return quot;cssTextquot;;   })();      // Later on:   window.onload = function(){     document.getElementsById(quot;test2quot;).setAttribute( STYLE_NAME,         document.getElementById(quot;testquot;).getAttribute( STYLE_NAME ) );   };   </script> 
  39. Know Your Enemies Points of Concern for JavaScript Code Browser Bugs Regressions Missing Features JavaScript Code Bug Fixes External Code, Markup
  40. 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 ✦
  41. 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(quot;onquot; + type, handle);   } 
  42. Safe Cross-Browser Fixes The easiest form of fix ✦ Unifies an API across browsers ✦ Implementation is painless ✦
  43. Unify Dimensions  // ignore negative width and height values   if ( (key == 'width' || key == 'height') &&  parseFloat(value) < 0 )     value = undefined; 
  44. Prevent Breakage  if ( name == quot;typequot; && elem.nodeName.toLowerCase()  == quot;inputquot; && elem.parentNode )     throw quot;type attribute can't be changedquot;; 
  45. 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. ✦
  46. Impractical to Test Performance-related issues ✦ Determining if Ajax requests will work ✦
  47. 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 ✦
  48. DOM Traversal Many methods of DOM traversal ✦ One unanimous solution: ✦ ✦ CSS Selector Engine
  49. 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
  50. 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 ✦
  51.    function find(selector, root){       root = root || document;              var parts = selector.split(quot; quot;),         query = parts[0],         rest = parts.slice(1).join(quot; quot;),         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;     } 
  52.  (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;     };   })(); 
  53. 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”
  54. Bottom-Up Some nice features: ✦ ✦ Only one DOM query ✦ No merge/unique required (except for “div, span”) ✦ Everything is a process of filtering
  55.    function find(selector, root){       root = root || document;              var parts = selector.split(quot; quot;),         query = parts[parts.length - 1],         rest = parts.slice(0,-1).join(quot;quot;).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;     } 
  56. 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 ✦
  57.  if ( typeof document.evaluate === quot;functionquot; ) {     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;     }   } 
  58. Goal CSS 3 XPath All Elements * //* All P Elements p //p All Child Elements p>* //p/* Element By ID #foo //*[@id='foo'] //*[contains(concat(quot; quot;, Element By Class .foo @class, quot; quot;),quot; foo quot;)] Element With Attribute *[title] //*[@title] First Child of All P p > *:first-child //p/*[0] All P with an A descendant //p[a] Not possible Next Element p+* //p/following-sibling::*[0]
  59. 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
  60.  <div id=quot;testquot;>     <b>Hello</b>, I'm a ninja!   </div>   <div id=quot;test2quot;></div>   <script>   window.onload = function(){     var divs = document.querySelectorAll(quot;body > divquot;);     assert( divs.length === 2, quot;Two divs found using a CSS selector.quot; );          var b = document.getElementById(quot;testquot;).querySelector(quot;b:only-childquot;);     assert( b, quot;The bold element was found relative to another element.quot; );  };   </script> 
  61.  <div id=quot;testquot;>     <b>Hello</b>, I'm a ninja!   </div>   <script>   window.onload = function(){     var b = document.getElementById(quot;testquot;).querySelector(quot;div bquot;);     assert( b, quot;Only the last part of the selector matters.quot; );  };   </script> 
  62. DOM Modification Injecting HTML ✦ Removing Elements ✦
  63. 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
  64.  function getNodes(htmlString){     var map = {       quot;<tdquot;: [3, quot;<table><tbody><tr>quot;, quot;</tr></tbody></table>quot;],       quot;<optionquot;: [1, quot;<select multiple='multiple'>quot;, quot;</select>quot;]       // a full list of all element fixes     };          var name = htmlString.match(/<w+/),       node = name ? map[ name[0] ] || [0, quot;quot;, quot;quot;];          var div = document.createElement(quot;divquot;);     div.innerHTML = node[1] + htmlString + node[2];          while ( node[0]-- )       div = div.lastChild;          return div.childNodes;   }      assert( getNodes(quot;<td>test</td><td>test2</td>quot;).length === 2,     quot;Get two nodes back from the method.quot; );   assert( getNodes(quot;<td>test</td>quot;).nodeName === quot;TDquot;,     quot;Verify that we're getting the right node.quot; ); 
  65. Element Mappings option and optgroup need to be contained in a ✦ <select multiple=quot;multiplequot;>...</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 a ✦ div<div>...</div>
  66. DocumentFragment Fragments can collect nodes ✦ Can be appended or cloned in bulk ✦ Super-fast (2-3x faster than normal) ✦
  67.    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(quot;divquot;);          insert(divs, [quot;Name:quot;], function(fragment){       this.appendChild( fragment );     });          insert(divs, [quot;First Lastquot;], function(fragment){       this.parentNode.insertBefore( fragment, this );     }); 
  68. 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
  69.  function globalEval( data ) {     data = data.replace(/^s+|s+$/g, quot;quot;);          if ( data ) {       var head = document.getElementsByTagName(quot;headquot;)[0] || document.documentElement,         script = document.createElement(quot;scriptquot;);              script.type = quot;text/javascriptquot;;       script.text = data;              head.insertBefore( script, head.firstChild );       head.removeChild( script );     }   } 
  70. Removing Elements Have to clean up bound events ✦ ✦ IE memory leaks Easy to do if it’s managed. ✦
  71. Events Three big problems with Events: ✦ ✦ Memory Leaks (in IE) ✦ Maintaining ‘this’ (in IE) ✦ Fixing event object inconsistencies
  72. 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”; };
  73. ‘this’ Users like having their ‘this’ refer to the ✦ target element Not the case in IE ✦ What’s the solution? ✦
  74. 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?
  75. 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
  76. Central Data Store Events Data Element function click(){} #43 function mouseover(){} Element Data Element #67 Element Events Element function click(){} #22 function click(){} Data Store
  77. Multiple Stores Element #43 Library A Element #67 Element Element #37 Element #22 Library B
  78. 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 ✦
  79. Questions? http://ejohn.org/ ✦ http://twitter.com/jeresig/ ✦
Advertisement