index.md (4928B)
1 --- 2 title: "Single-Use jQuery Plugins" 3 date: 2009-07-16T00:00:00+00:00 4 draft: false 5 canonical_url: https://www.viget.com/articles/single-use-jquery-plugins/ 6 --- 7 8 One of the best features of [jQuery](http://jquery.com/) is its simple, 9 powerful plugin system. The most obvious reason to write a plugin is the 10 same reason you'd create a Rails plugin: to package up functionality for 11 reuse. While code reuse between projects is certainly a worthy goal, it 12 sets a prohibitively high bar when deciding whether or not to pull a 13 piece of functionality into a plugin. 14 15 There are a number of good reasons to create jQuery plugins for behavior 16 specific to the app under development. Consider the following example, a 17 simple plugin to create form fields for an arbitrary number of nested 18 resources, adapted from a recent project: 19 20 ```javascript 21 (function($) { 22 $.fn.cloneableFields = function() { 23 return this.each(function() { 24 var container = $(this); 25 var fields = container.find("fieldset:last"); 26 var label = container.metadata().label || "Add"; 27 28 container.count = function() { 29 return this.find("fieldset").size(); 30 }; 31 32 // If there are existing entries, hide the form fields by default 33 if (container.count() > 1) { 34 fields.hide(); 35 } 36 37 // When link is clicked, add a new set of fields and set their keys to 38 // the total number of fieldsets, e.g. instruction_attributes[5][name] 39 var addLink = $("<a/>").text(label).click(function() { 40 var html = fields.html().replace(/\[\d+\]/g, "[" + container.count() + "]"); 41 $(this).before("<fieldset>" + html + "</fieldset>"); 42 return false; 43 }); 44 45 container.append(addLink); 46 }); 47 }; 48 })(jQuery); 49 ``` 50 51 ## Cleaner Code 52 53 When I was first starting out with jQuery and unobtrusive JavaScript, I 54 couldn't believe how easy it was to hook into the DOM and add behavior. 55 I ended up with monstrous `application.js` files consisting solely of 56 giant `$(document).ready()` functions --- exactly the kind of spaghetti 57 code I switched to Ruby and Rails to avoid. A pre-refactoring version of 58 [SpeakerRate](http://www.speakerrate.com) had one over 700 lines long. 59 60 By pulling this feature into a plugin, rather than some version of the 61 above code in our `$(document).ready()` function, we can stash it in a 62 separate file and replace it with a single line: 63 64 ```javascript 65 $("div.cloneable").cloneableFields(); 66 ``` 67 68 Putting feature details into separate files turns our `application.js` 69 into a high-level view of the behavior of the site. 70 71 ## State Maintenance 72 73 In JavaScript, functions created inside of other functions maintain a 74 link to variables declared in the outer function. In the above example, 75 we create variables called `container` and `fields` when the page is 76 loaded, and then access those variables in the `click()` handler of the 77 inserted link. This way, we can avoid performing potentially expensive 78 jQuery selectors every time an event is fired. 79 80 Right now, you might be thinking, "But David, isn't 81 `$(document).ready()` also a function? Shouldn't this same principle 82 apply?" Yes and no, dear reader. Variables declared in 83 `$(document).ready()` can be accessed by functions declared there, but 84 since it's only called once, there will only be one copy of those 85 variables for the page. By using the standard `return this.each()` 86 plugin pattern, we ensure that there will be a copy of our variables for 87 each selector match, so that we can have multiple sets of 88 CloneableFields on a single page. 89 90 ## Faster Scripts 91 92 Aside from being able to store the results of selectors in variables, 93 there are other performance gains to be had by containing your features 94 in plugins. If a behavior involves attaching event listeners to five 95 different DOM elements, rather than running selectors to search for each 96 of these elements when the page loads, we'll get better performance by 97 searching for a containing element and then invoking our plugin on it, 98 since we'll only have to make one call on pages that don't have the 99 feature. Furthermore, inside your plugin, you'll be more inclined to 100 scope your selectors properly, further increasing performance. 101 102 If you opt to put your features into separate files, make sure compress 103 all your JavaScript into one file in production to reduce the number of 104 HTTP requests. 105 106 ## Conclusion 107 108 As Rubyists, the reasons to package up jQuery features follow many of 109 the ideas to which we already subscribe: DRY, separation of concerns, 110 and idiomatic code. Using jQuery plugins is by no means the only way to 111 achieve clean JavaScript; the April edition of 112 [JSMag](http://www.jsmag.com/main.issues.description/id=19/) has a great 113 article about containing features within object literals, a more 114 framework-agnostic approach. Whatever method you choose, do *something* 115 to avoid the novel-length `$(document).ready()` function. Your future 116 self will thank you for it.