Plugins

There are a great number of really impressive JavaScript libraries in the wild. For me the library that changed how I look at JavaScript was jQuery. For others it may have been one of the other popular libraries such as MooTool, Dojo, Prototype, or YUI. However, jQuery has exploded in popularity and has, at the time of writing, won the JavaScript library wars. 78.5% of the top ten thousand websites, by traffic, on the internet make use of some version of jQuery. None of the rest of the libraries even breaks 1%.

Many developers have seen fit to implement their own libraries on top of these foundational libraries in the form of plugins. A plugin typically modifies the prototype exposed by the library and adds additional functionality. The syntax is such that, to the end developer, it appears to be part of the core library.

How plugins are built varies depending on the library you're trying to extend. Nonetheless, let's take a look at how we can build a plugin for jQuery and then for one of my favourite libraries, d3. We'll see if we can extract some commonalities.

jQuery

At jQuery's core is the CSS selector library called sizzle.js. It is sizzle that is responsible for all the really nifty ways jQuery can select items on a page using CSS3 selectors. Use jQuery to select elements on a page like so:

$(":input").css("background-color", "blue");

Here, a jQuery object is returned. The jQuery object acts a lot like, although not completely like, an array. This is achieved by creating a series of keys on the jQuery object numbered 0 through to n-1 where n is the number of elements matched by the selector. This is actually pretty smart as it enables array like accessors:

$($(":input")[2]).css("background-color", "blue");

While providing a bunch of additional functions, the items at the indices are plain HTML Elements and not wrapped with jQuery, hence the use of the second $().

For jQuery plugins, we typically want to make our plugins extend this jQuery object. Because it is dynamically created every time the selector is fired we actually extend an object called $.fn. This object is used as the basis for creating all jQuery objects. Thus creating a plugin that transforms all the text in inputs on the page into uppercase is nominally as simple as the following:

$.fn.yeller = function(){
  this.each(function(_, item){
    $(item).val($(item).val().toUpperCase());
    return this;
  });
};

This plugin is particularly useful for posting to bulletin boards and for whenever my boss fills in a form. The plugin iterates over all the objects selected by the selector and converts their content to uppercase. It also returns this. By doing so we allow for chaining of additional functions. You can use the function like so:

$(function(){$("input").yeller();});

It does rather depend on the $ variable being assigned to jQuery. This isn't always the case as $ is a popular variable in JavaScript libraries, likely because it is the only character that isn't a letter or a number and doesn't already have special meaning.

To combat this, we can use an immediately evaluated function in much the same way we did way back in Chapter 2, Organizing Code:

(function($){
  $.fn.yeller2 = function(){
    this.each(function(_, item){
      $(item).val($(item).val().toUpperCase());
      return this;
    });
  };
})(jQuery);

The added advantage here is that, should our code require helper functions or private variables, they can be set inside the same function. You can also pass in any options required. jQuery provides a very helpful $.extend function that copies properties between objects, making it ideal for extending a set of default options with those passed in. We looked at this in some detail in a previous chapter.

The jQuery plugin documentation recommends that the jQuery object be polluted as little as possible with plugins. This is to avoid conflicts between multiple plugins that want to use the same names. Their solution is to have a single function that has different behaviours depending on the parameters passed in. For instance, the jQuery UI plugin uses this approach for dialog:

$(«.dialog»).dialog(«open»);
$(«.dialog»).dialog(«close»);

I would much rather call these like the following:

$(«.dialog»).dialog().open();
$(«.dialog»).dialog().close();

With dynamic languages there really isn't a great deal of difference but I would much rather have well named functions that can be discovered by tooling than magic strings.

d3

d3 is a great JavaScript library that is used for creating and manipulating visualizations. For the most part, people use d3 in conjunction with scalable vector graphics to produce graphics such as this hexbin graph by Mike Bostock:

d3

d3 attempts to be non-opinionated about the sorts of visualizations it creates. Thus there is no built-in support for creating such things as bar charts. There is, however, a collection of plugins that can be added to d3 to enable a wide variety of graphs including the hexbin one shown in the preceding figure.

Even more, the jQuery d3 places emphasis on creating chainable functions. For example, this code is a snippet that creates a column chart. You can see that all the attributes are being set through chaining:

var svg = d3.select(containerId).append("svg")
var bar = svg.selectAll("g").data(data).enter().append("g");
bar.append("rect")
.attr("height", yScale.rangeBand()).attr("fill", function (d, _) {
  return colorScale.getColor(d);
})
.attr("stroke", function (d, _) {
  return colorScale.getColor(d);
})
.attr("y", function (d, i) {
  return yScale(d.Id) + margins.height;
})

The core of d3 is the d3 object. Off that object hang a number of namespaces for layouts, scales, geometry, and numerous others. As well as whole namespaces, there are functions for doing array manipulation and loading data from external sources.

Creating a plugin for d3 starts with deciding where we're going to plug into the code. Let's build a plugin that creates a new color scale. A color scale is used to map a domain of values to a range of colors. For instance, we might wish to map the domain of the following four values onto a range of four colors:

d3

Let's plug in a function to provide a new color scale, in this case one that supports grouping elements. A scale is a function that maps a domain to a range. For a color scale, the range is a set of colors. An example might be a function that maps all even numbers to red and all odd to white. Using this scale on a table would result in zebra striping:

d3.scale.groupedColorScale = function () {
  var domain, range;

  function scale(x) {
    var rangeIndex = 0;
    domain.forEach(function (item, index) {
      if (item.indexOf(x) > 0)
        rangeIndex = index;
    });
    return range[rangeIndex];
  }

  scale.domain = function (x) {
    if (!arguments.length)
      return domain;
    domain = x;
    return scale;
  };

  scale.range = function (x) {
    if (!arguments.length)
      return range;
    range = x;
    return scale;
  };
  return scale;
};

We simply attach this plugin to the existing d3.scale object. This can be used by simply giving an array of arrays as a domain and an array as a range:

var s = d3.scale.groupedColorScale().domain([[1, 2, 3], [4, 5]]).range(["#111111", "#222222"]);
s(3); //#111111
s(4); //#222222

This simple plugin extends the functionality of d3's scale. We could have replaced existing functionality or even wrapped it such that calls into existing functionality would be proxied through our plugin.

Plugins are generally not that difficult to build but they do vary from library to library. It is important to keep an eye on the existing variable names in libraries so we don't end up clobbering them or even clobbering functionality provided by other plugins. Some suggest prefixing functions with a string to avoid clobbering.

If the library has been designed with it in mind there may be additional places into which we can hook. A popular approach is to provide an options object that contains optional fields for hooking in our own functions as event handlers. If nothing is provided the function continues as normal.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.143.3.208