Slideshow transcript
Slide 1: Secrets of JavaScript Libraries (Left to Right) Sam Stephenson (Prototype) Alex Russell (Dojo) Thomas Fuchs (Script.aculo.us) Andrew Dupont (Prototype) John Resig (jQuery)
Slide 2: What to Cover ✦ Topics: ✦ JavaScript Language ✦ Cross-Browser Code ✦ Events ✦ DOM Traversal ✦ Style ✦ Animations ✦ Distribution ✦ HTML Insertion
Slide 3: Secrets of the JavaScript Language
Slide 4: // Set up a class and create an element var Clock = Class.create({ initialize: function() { this.createElement(); }, createElement: function() { this.element = new Element(\"div\"); } });
Slide 5: // Display the time var Clock = Class.create({ initialize: function() { this.createElement(); }, createElement: function() { this.element = new Element(\"div\"); var date = new Date(); this.element.update( date.getHours() + \":\" + date.getMinutes().toPaddedString(2) + \".\" + date.getSeconds().toPaddedString(2) ); } }); $(document.body).insert(new Clock().element);
Slide 6: // Add the timer var Clock = Class.create({ initialize: function() { this.createElement(); this.createTimer(); }, createElement: function() { this.element = new Element(\"div\"); }, updateElement: function() { var date = new Date(); this.element.update( date.getHours() + \":\" + date.getMinutes().toPaddedString(2) + \".\" + date.getSeconds().toPaddedString(2) ); }, createTimer: function() { window.setInterval(500, this.updateElement.bind(this)); } });
Slide 7: // Add some options var Clock = Class.create({ color: \"black\", format: \"#{hour}:#{minute}.#{second}\", initialize: function(options) { Object.extend(this, options); this.createElement(); this.createTimer(); }, createElement: function() { this.element = new Element(\"div\"); this.element.setStyle({ color: Clock().element); $(document.body).insert(new this.color }); }, $(document.body).insert(new Clock({ color: \"red\" }).element); $(document.body).insert(new Clock({ format: \"#{hour}:#{minute}\" }).element); updateElement: function() { this.element.update(this.format.interpolate(this.getTime())); }, getTime: function() { var date = new Date(); return { hour: date.getHours(), minute: date.getMinutes().toPaddedString(2), second: date.getSeconds().toPaddedString(2) } }, ...
Slide 8: // Use #toElement var Clock = Class.create({ ... toElement: function() { return this.element; } }); $(document.body).insert(new Clock()); $(document.body).down(\"div.clock > div\").replace(new Clock()); $(document.body).down(\"div.clock\").update(new Clock());
Slide 9: // Subclass it var AmPmClock = Class.create(Clock, { format: \"#{hour}:#{minute}:#{second} #{ampm}\", getTime: function($super) { var time = $super(); time.ampm = time.hour < 12 ? \"am\" : \"pm\"; if (time.hour == 0) { time.hour = 12; } else if (time.hour > 12) { time.hour -= 12; } return time; } }); $(document.body).insert(new AmPmClock());
Slide 10: // Or monkeypatch it Object.extend(Clock.prototype, { format: \"#{hour}:#{minute}:#{second} #{ampm}\", getTime: Clock.prototype.getTime.wrap(function(original) { var time = original(); time.ampm = time.hour < 12 ? \"am\" : \"pm\"; if (time.hour == 0) { time.hour = 12; } else if (time.hour > 12) { time.hour -= 12; } return time; } }); $(document.body).insert(new Clock());
Slide 11: Secrets of Cross-Browser Code
Slide 12: Browser Sniffing (wait, hear me out)
Slide 13: Conditionally evil
Slide 14: In order of desirability:
Slide 15: capabilities & quirks
Slide 16: Capabilities are easy to sniff
Slide 17: Quirks are... more troublesome
Slide 18: Object detection (for capabilities) if (document.evaluate) { // ... }
Slide 19: Distill to a boolean (for stuff in the gray area) var thinksCommentsAreElements = false; if ( document.createElement('!') ) { thinksCommentsAreElements = true; }
Slide 20: ...then sniff (for outright bugs/quirks) if (Prototype.Browser.IE) { element.style.filter = \"alpha(opacity=50);\"; }
Slide 21: Try to do the right thing, but don’t stand on principle
Slide 22: The social contract Good faith from browser makers ➝ good faith from web authors
Slide 23: Secrets of Quality Assurance
Slide 26: 1,500 750 0 1.5.0 1.5.1 1.6.0.2
Slide 28: Debugging ✦ Localize & Reproduce ✦ Find the smallest possible code that generates the problem ✦ Easy test-case generation ✦ ./gen.sh 1245 ajax ./gen.sh 1246 dom ✦ Simple logging, all browsers: document.getElementById(“output”) .innerHTML += “<br>” + msg;
Slide 29: Secrets of Events
Slide 30: Event handlers are tricky
Slide 31: Don’t store them on the element itself circular references = sadness
Slide 32: Build a global hashtable (like jQuery does it)
Slide 33: Element Data Store ✦ Each element gets a unique ID (bound w/ a unique property) elem.jQuery12345 = 1; ✦ Points back to large data structure: data[1] = { ... all element data here ... }; ✦ Data (like event handlers) are stored here data[1] = { handlers: { click: [ function(){...} ], ... } };
Slide 34: Then clean up on page unload So that IE doesn’t keep them in memory in perpetuum
Slide 35: Fixing memory leaks
Slide 36: Internet Explorer 6 red-headed stepchild
Slide 37: Don’t “prove” your code has no leaks (view the results!)
Slide 38: Drip is awesome
Slide 39: Test page: Create a bunch of elements, assign each an event handler, then remove each one from the page
Slide 40: Demonstrating the Leak Notice the stair-step effect
Slide 41: Plugging the leak // In Prototype, will remove all event listeners from an element Event.stopObserving(someElement); Event.purgeObservers = function(element, includeParent) { Element.select(element, \"*\").each(Event.stopObserving); if ( includeParent ) Event.stopObserving( element ); };
Slide 42: Redefining functions add “before advice” to functions that need to remove elements
Slide 43: Code sample Element.Methods.update = Element.Methods.update.wrap( function(proceed, element, contents) { Event.purgeObservers(element); return proceed(element, contents); } ); Element.Methods.replace = Element.Methods.replace.wrap( function(proceed, element, contents) { Event.purgeObservers(element, true); return proceed(element, contents); } ); Element.Methods.remove = Element.Methods.remove.wrap( function(proceed, element) { Event.purgeObservers(element, true); return proceed(element); } ); Element.addMethods();
Slide 44: Drop-in fix for Prototype 1.6
Slide 45: Custom Events • Everything is event based if you squint • DOM is a good foundation • Terrible for stitching together non-DOM components and code • Composition == good, inheritance == bad • Custom events let us join loosely • When to use them? Pitfalls?
Slide 46: Custom Events (contd.) // in Dojo: dojo.subscribe(“/foo”, function(e, arg){ ... }); dojo.publish(“/foo”, [{ data: “thinger”}, “second arg”]); // in Prototype: document.observe(“event:foo”, function(e){ ... }); $(“nodeId”).fire(“event:foo”, { data: “thinger” }); // in jQuery: $(document).bind(“foo”, function(e, data, arg){ ... }); $(document).trigger(“foo”, [{ data: “thinger”}, “second”]);
Slide 47: Secrets of DOM Traversal
Slide 48: Selector Internals • Optimized DOM • Top-down vs. bottom-up • Caching + winnowing • XPath • Native, aka: querySelectorAll()
Slide 49: Selector Internals • Tradeoffs: size vs. speed vs. complexity • Prototype: XPath when possible, else DOM • Good perf, lower complexity, hackable • Dojo: all 3 methods, picks best available • Best perf, biggest, highest complexity • JQuery: DOM • Good perf, small size, extensiblity vs. forward-compat tradeoff
Slide 50: Secrets of Style
Slide 51: Computed Style ✦ IE way vs. Everyone else ✦ IE returns “actual value” ✦ Everyone else returns “pixel value” ✦ font-size: 2em; IE: “2em” Other: 24 ✦ Need to convert to common base ✦ Performance: Very costly, generally avoided wherever possible
Slide 52: Pixel Values in IE ✦ if ( !/^\\d+(px)?$/i.test( ret ) && /^\\d/.test( ret ) ) { // Remember the original values var style = elem.style.left, runtimeStyle = elem.runtimeStyle.left; // Put in the new values to get a computed value out elem.runtimeStyle.left = elem.currentStyle.left; elem.style.left = ret || 0; ret = elem.style.pixelLeft + “px”; // Revert the changed values elem.style.left = style; elem.runtimeStyle.left = runtimeStyle; } ✦ From Dean Edwards: http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
Slide 53: Computed Style ✦ Safari 2 & 3: Giant mess for display: none elements ✦ Safari 2 ✦ getComputedStyle() returns undefined ✦ Safari 3 ✦ Always return empty strings for value ✦ Much harder to detect var ret = document.defaultView.getComputedStyle( elem, null ); return !ret || ret.getPropertyValue(”color”) == “”;
Slide 54: Finding dimensions
Slide 55: Dimensions of what? content box, border box, margin box...
Slide 56: DHTML properties clientWidth, offsetWidth
Slide 57: The value you want is not captured by any property Lorem ipsum dolor sit amet. width clientWidth offsetWidth
Slide 58: Computed styles getting padding & border value
Slide 59: The fool-proof, painful way Take the offsetWidth, then subtract computed padding & border
Slide 60: Code example Element.getCSSWidth = function(element) { element = $(element); return element.offsetWidth - parseFloat(element.getStyle(\"borderLeft\")) - parseFloat(element.getStyle(\"paddingLeft\")) - parseFloat(element.getStyle(\"paddingRight\")) - parseFloat(element.getStyle(\"borderRight\")); };
Slide 61: Secrets of Animation
Slide 62: Old-School “Effects”
Slide 63: setInterval
Slide 64: Events-based
Slide 65: Secrets ofJavaScript Deployment
Slide 66: CONTENT EXPIRATION
Slide 69: Concatenation
Slide 71: GZIP 4-5x smaller
Slide 72: Packaging • Dev vs. deployment constraints • No library a single file, but all ship that way • # of requests largest constraint • Sync vs. async • Static resource servers + CDNs • Dependency management matters! • Runtime vs. deployment
Slide 73: Packaging // in Dojo: dojo.provide(“foo.bar.Baz”); dojo.require(“dojox.dtl”); // in GWT: package com.foo.bar; import com.foo.bar.Blah; // in JSAN: JSAN.use(“foo.bar.Blah”); // exports handled by build tools
Slide 74: Packaging • The build-process divide • Library support vs. server concat/shrink • Can we strip “dead” code? • Social artifacts of packaging systems
Slide 75: HTML Insertion ✦ $(“div”).append(“<b>foo</b>”); ✦ Convert HTML String ✦ innerHTML ✦ Range: .createContextualFragment() ✦ Must purify first: ✦ Fix <table>s for IE (<tbody> wrap) ✦ Handle <option>s (contain in <select>)
Slide 76: HTML Insertion ✦ // Fix “XHTML ”-style tags in all browsers elem = elem.replace(/(<(\\w+)[^>]*?)\\/>/g, function(all, front, tag){ return tag.match(/^(abbr|br|col|img|input|link|meta|param| hr|area|embed)$/i) ? all : front + “></” + tag + “>”; }); ✦ $(“<abbr/>”).html(“hello!”).appendTo(“#foo”);
Slide 77: Script Execution ✦ Execute Script in Global Scope ✦ var head = document.getElementsByTagName(”head”)[0] || document.documentElement, script = document.createElement(”script”); script.type = “text/javascript”; if ( jQuery.browser.msie ) script.text = data; else script.appendChild( document.createTextNode( data ) ); head.appendChild( script ); head.removeChild( script );
Slide 78: Questions ✦ Panelists: ✦ John Resig (ejohn.org) ✦ Sam Stephenson (conio.net) ✦ Alex Russell (alex.dojotoolkit.org) ✦ Thomas Fuchs (script.aculo.us/thomas) ✦ Andrew Dupont (andrewdupont.net) ✦ Frameworks: ✦ Prototype (prototypejs.org) ✦ jQuery (jquery.com) ✦ Dojo (dojotoolkit.org) ✦ Script.aculo.us (script.aculo.us)


Add a comment on Slide 1
If you have a SlideShare account, login to comment; else you can comment as a guest- Favorites & Groups
Showing 1-50 of 40 (more)