So we must search the browser’s user-agent string to see whether it uses the affected engine—then we must look at the release year of the engine to figure out whether it’s old enough to be affected. Of course, Prototype fixes this so that you, as a developer, need not worry about it. And the quirks you’ll encounter probably won’t be so tenacious. But eventually, if you write enough code, you’ll need to do some occasional browser sniffing. Do it, apologize to yourself, and move on. If it makes you queasy, good! It should make you queasy. That’ll stop you from using it more often than you ought to. And the fact that you’re ashamed of your old poetry sim- ply affirms the sophistication of your adult tastes. If You Must . . . So if you’ve got no other options . . . yes, it’s OK to sniff. But you’ve got to do it right. Perform the following steps or else suffer the wrath of the browser gods. Get a Second Opinion First, assuage your guilt. Find a friend or coworker—across the hall, on Instant Messen- ger, in IRC—and summarize your dilemma. Often your consultant will suggest an approach you hadn’t thought of. But if he can’t think of a better way, you’ll feel more secure in your decision. Take Notes Write a comment that explains the problem and why you’ve got to sniff. Put it as close as possible to the offending line of code. Be verbose. This is for your own benefit: six months from now you won’t remember why you wrote that code the way you did, so think of it as a message to Future You. Walk a Straight Code Path Most importantly, write code without unnecessary kinks and contortions. If Internet Explorer needs one thing, but all other browsers need another, write your function to handle the other browsers. If one approach uses the DOM standard and the other uses a proprietary Internet Explorer method, write your function to use the DOM standard— then, at the beginning of the function, send Internet Explorer into a different function to handle the weird case. CHAPTER 13 ■ PROTOTYPE AS A PLATFORM 309 The purpose is to avoid the “black holes” that come from excessive sniffing. Consider this code: function handleFoo() { if (navigator.userAgent.match(/Gecko\//)) return handleFoo_Firefox(); if (navigator.userAgent.match(/MSIE/)) return handleFoo_MSIE(); } Safari, Opera, OmniWeb, iCab, and browsers far off the beaten path will fall straight through this function—because your code never “caught” them. Again, you’re not con- cerned with what the browser is; you’re concerned with what it says it can do. You can’t possibly test in every browser on earth, so embrace standards as a compromise: if a browser follows web standards, it ought to be able to read your code, even if you didn’t code with it in mind. Holding Up Your End of the Bargain Even though I’m giving you permission to write “dirty” code once in a while, I mean to open only the tiniest of loopholes. The early days of JavaScript taught us that bad things happen when developers abuse user-agent sniffing. I’d recommend against it altogether if it weren’t for the handful of edge cases that require sniffing. In other words, when we as developers sniff unnecessarily, it’s our fault. When we discover situations in which sniffing is the only option, it’s the browser maker’s fault. So think of developing with web standards as a social contract between developers and vendors: do your part, and we’ll do ours. Make your scripting environment behave pre- dictably and rationally, and we won’t need to take drastic steps to code around bugs. Making and Sharing a Library Written something useful? Something you think would be valuable to others? All the major JavaScript toolkits have an ecosystem of plug-ins and add-ons. If you’ve created a script that makes your life easier, there’s a good chance it will make someone else’s life easier, too. Maybe you’ve done something big, like a really cool UI widget or client-side charting. Maybe it’s big enough to warrant a Google Code project and a release schedule. Or maybe you’ve written a way to do simple input validation in 30 lines of code and just want to put it on the Web, as is, so that others can learn from it. Here are some best practices for releasing your add-on. Most of them relate to the difficulty of writing code that satisfies both your needs and the needs of the public. CHAPTER 13 ■ PROTOTYPE AS A PLATFORM310 Make Your Code Abstract The hardest thing about turning your code into public code is handling abstraction. When you first wrote it, you might have embraced the conventions of your own circum- stances in order to simplify things; now you’ve got to go back and handle scenarios you didn’t foresee. Do One Thing Well (or Else Go Modular) Don’t try to be a Swiss Army knife. Code that does one thing well is easier to understand, easier to set up, and faster for the end user to download. It’s one thing to write a 5 KB script that depends on Prototype; it’s another thing to write 80 KB of JavaScript that depends on Prototype, most of which John Q. Developer won’t even need. I should clarify: it’s fine to do many things well, as long as those things are not interdependent. If your script does three unrelated things, break it up into three unre- lated scripts. Bundle them together if you like, but don’t require all three unless you’ve got a very good reason. Notice that script.aculo.us is modular: you don’t have to load all the UI stuff if the effects are all you want. Embrace Convention With apologies to Jakob Nielsen, a developer will spend far more time working with other people’s code than with your code. Each major framework has a distinct coding style that can be seen in the structure of its API, the order of arguments, and even its code formatting. (Spaces, not tabs! No—tabs, not spaces!) Follow those conventions! Make your add-on feel like a part of Prototype. Design your classes to take an options argument. Embrace the patterns, coding style, and lexi- con. Nobody reads the documentation (at least not at first), so even the smallest details of code writing can help a person intuit how to use your code. Make Things Configurable All of the classes in Prototype and script.aculo.us share a powerful design principle: they’re able to be exhaustively configurable and dead-simple to use at the same time. Is there anything about your code that someone might need to tailor to his needs? Does your widget set a background color? Make it a configurable option. Does your date picker control support arbitrary date formats (like DD/MM/YYYY, which is the most common format outside of North America)? If not, write it now; someone will ask for that feature. Does your widget fade out over a span of 0.5 seconds? Someone will argue pas- sionately for it to be 1 second instead. CHAPTER 13 ■ PROTOTYPE AS A PLATFORM 311 That takes care of the “exhaustively configurable” part. To make your add-on dead- simple to use, take care to hide this complexity below the surface until it’s needed. Make as many options as you like, but give an intelligent default for each one. Make sure the options argument can be omitted together. They’re called options because they’re optional; if a particular parameter can’t have a default and can’t be omit- ted, move it out of the options object and into a positional argument. Add Hooks Often your add-on will be an almost-but-not-quite-perfect solution to someone’s prob- lem. “If only the tooltips stayed in the DOM tree after they fade out!” “This would be perfect if the widget could follow my cursor around.” These requests are usually too complex or obscure than can be solved with extra configuration, but they’re important nonetheless. Don’t bring your fellow developer 95 percent of the way to his destination, and then leave him stranded in the mysterious town of Doesn’t-Quite-Do-What-I-Want. Give him the tools to travel that final 5 percent on his own. There are two prevailing ways to empower the user to make those tweaks: callbacks and custom events. We’ve seen callbacks before in nearly all the script.aculo.us controls. They’re func- tions the user can define that will get called at a certain point in the control’s operation. They can be called at certain points in your add-on’s life cycle. For instance, we can leverage the Configurable mixin we wrote earlier to set up default callbacks that are “empty,” letting the user override them if necessary: var Widget = Class.create(Configurable, { initialize: function(element, options) { this.element = $(element); this.setOptions(options); this.options.onCreate(); } }); Widget.DEFAULT_OPTIONS = { onCreate: Prototype.emptyFunction }; // using the callback var someWidget = new Widget('some_element', { onCreate: function() { console.log('creating widget'); } }); CHAPTER 13 ■ PROTOTYPE AS A PLATFORM312 Notice the reference to Prototype.emptyFunction: it’s a function that does nothing. Instead of checking whether the onCreate option exists, we include it in the default options; this way, whether the user specifies it or not, the onCreate property refers to a function. Custom events are a way to approach this problem from a different angle. Imagine a tooltip:hidden event that a developer can listen for in order to apply her own logic for dealing with hidden tooltips. Firing a custom event is a lot like invoking a callback: var Widget = Class.create({ initialize: function(element, options) { this.element = $(element); // fire an event and pass this instance as a property of // the event object this.element.fire("widget:created", { widget: this }); } }); // using the custom event $('some_element').observe('widget:created', function() { console.log("widget created"); }); // or observe document-wide: document.observe("widget:created", function() { console.log("widget created"); }); var someWidget = new Widget('some_element'); They’re a little more work to set up, but they’re also more robust. Notice that we can listen for the event on the element itself or on any one of its parent nodes, all the way up to document. Callbacks don’t provide that kind of flexibility, nor do they allow you to easily attach more than one listener. Whichever way you go, be sure to document your hooks. They won’t get used if nobody knows they exist. Be clear about when the hook fires and what information accompanies it—parameters passed to the callback or properties attached to the event object. CHAPTER 13 ■ PROTOTYPE AS A PLATFORM 313 Summary In this chapter, we explored a number of ways to turn code that’s useful to you into code that’s useful to others as well. Conciseness, modularity, documentation, and extensibility are the only things separating the scripts you write from libraries like Prototype and script.aculo.us. Have you got an idea for a script? A UI control, a clever use of Ajax, or even a way to automate a repetitive task? Write it! Get your code out into the wild! Focusing on the pol- ish needed to write scripts for the public will make you a better developer. CHAPTER 13 ■ PROTOTYPE AS A PLATFORM314 . Configurable All of the classes in Prototype and script .aculo. us share a powerful design principle: they’re able to be exhaustively configurable and dead-simple to use at the same time. Is there. developing with web standards as a social contract between developers and vendors: do your part, and we’ll do ours. Make your scripting environment behave pre- dictably and rationally, and we won’t need. documentation, and extensibility are the only things separating the scripts you write from libraries like Prototype and script .aculo. us. Have you got an idea for a script? A UI control, a clever use of