davideisinger.com

My personal website
Log | Files | Refs | README

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.