Code Review: jQuery Waypoints
Code Review is a series on DailyJS where I take a look at an open source project to see how it’s built. Along the way we’ll learn patterns and techniques by JavaScript masters. If you’re looking for tips to write better apps, or just want to see how they’re structured in established projects, then this is the tutorial series for you.
jQuery plugins are often distributed in a state that could be easily improved upon. It’s common to see them without much in the way of documentation, licensing, or tests. I’ve written about jQuery Waypoints before on DailyJS, and I remember being impressed by it. Let’s take a look at what sets this jQuery plugin apart.
About jQuery Waypoints
jQuery Waypoints (GitHub: imakewebthings / jquery-waypoints, License: MIT and GPL) by Caleb Troughton makes it easy to execute a function whenever an element is scrolled to. It can be used to create sticky elements, infinite scrolling, or many other novel user interface enhancements.
Even though it’s a relatively simple plugin, it has a website, documentation, examples, a GitHub page, appropriate licensing, and even unit tests! It’s also extremely popular, with 469 watchers on GitHub.
Usage
To trigger a function when an element is scrolled to, just use the waypoint function:
$('.entry').waypoint(function() {
alert('You have scrolled to an entry.');
});
There are also global functions, like $.waypoints('viewportHeight') and $.waypoints('refresh').
Structure
The project’s source is contained in one file, waypoints.js. The author also distributes a minimised build.
The first thing I noticed about this project was the detailed comments. Most variables and functions have been documented, which makes it extremely easy to follow the source (and generate documentation).
A classic anonymous wrapper is used to safely house the plugin, but this one has parameters:
(function($, wp, wps, window, undefined){
// Plugin goes here
})(jQuery, 'waypoint', 'waypoints', this);
Even though these variables aren’t dynamic, they give the author (or contributors) some flexibility and makes the code feel a little bit more succinct. Most of the plugin’s “state” variables are kept within this function.
The methods exposed by the plugin are set in an object called methods. The appropriate method is delegated at runtime based on this object when jQuery.fn.waypoint is called:
var methods = {
init: function(f, options) {
// ... snip
}
};
$.fn[wp] = function(method) {
if (methods[method]) {
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
// ... snip
This adds jQuery.fn.waypoint and uses apply so this will be the current jQuery object in methods. This is a neat little delegation pattern often used by jQuery plugins.
One reason this pattern has been used is because jQuery’s authors recommend avoiding polluting jQuery’s namespace with lots of methods. Rather than naming methods like this: jQuery.fn.waypointRemove the following should be used: jQuery.fn.waypoint('remove'). Options can still be passed, which is why Array.prototype.slice is used to prepare the method’s arguments.
Further down the file jQMethods is used in a very similar fashion to set up the $.waypoints() methods:
$[wps] = function(method) {
if (jQMethods[method]) {
return jQMethods[method].apply(this);
}
else {
return jQMethods["aggregate"]();
}
};
Finally, default settings are defined and some events are bound to window.
Code Style
I noticed that Caleb uses $varName to denote objects that have been wrapped by jQuery:
var $this = $(this);
This should stop people from accidentally calling jQuery more than once.
I don’t think there’s any duplication of jQuery’s built-in functionality; he uses $.error(), $.grep, and $.each. I’ve seen a few plugins resort to Underscore for anything relating to iterators.
He also correctly uses === and !==, so I imagine he’s a fan of JavaScript: The Good Parts.
Tests
The tests are written with Jasmine and jasmine-jquery, which means they can be executed in a browser: jQuery Waypoint tests.
They’re written in a BDD style:
describe('jQuery Waypoints', function() {
// ... snip
it('should create a waypoint', function() {
expect($.waypoints().length).toEqual(1);
});
Conclusion
When writing jQuery plugins it’s a good idea to take a look at existing ones for some guidance. jQuery Waypoints has some tried and tested patterns that work well. It also includes tests and documentation!
